Skip to content

Add unit tests for ironic.py pure helpers#2342

Merged
ideaship merged 1 commit into
mainfrom
implement/issue-2226-ironic-helper-tests
Jul 2, 2026
Merged

Add unit tests for ironic.py pure helpers#2342
ideaship merged 1 commit into
mainfrom
implement/issue-2226-ironic-helper-tests

Conversation

@berendt

@berendt berendt commented Jun 10, 2026

Copy link
Copy Markdown
Member

Closes #2226.

Adds tests/unit/tasks/conductor/test_ironic_helpers.py covering the pure
helpers and the _prepare_node_attributes builder in
osism/tasks/conductor/ironic.py — everything testable without the full
sync orchestration (the sync entry points stay in the companion sub-issue).

Change set

A single new test module (no production code touched), organized by helper:

  • _derive_as_from_hostname_yrzn — storage vs. non-storage type digit,
    < 5 parts → None, single-digit padding, already-two-digit values left
    alone.
  • _get_metalbox_primary_ip4_fallback — invalid YAML, non-list setting,
    non-dict element skipped, tag stripped + role="metalbox" added (asserted
    via captured filter kwargs), first/second metalbox IP selection, and the
    two no-IP / no-metalbox warning paths.
  • _get_metalbox_primary_ip4 — no OOB IP (fallback not called), subnet
    match, no-match → fallback, matching subnet but no primary_ip4 → early
    None (no fallback), match on the second interface, and IPv4/IPv6 mixing.
  • _render_templates — flat/nested dict, nested list, list of dicts,
    non-template and non-string values untouched, multiple vars, in-place
    mutation returning None.
  • _prepare_node_attributes — base/config-context/custom-field merging
    and order, driver pruning, template variables (board creds, OOB address,
    ironic_osism_* propagation), osism-ipa-type=yrzn001 kernel enrichment
    (frr params, metalbox, AS fallback, unknown type), skip_kernel_params,
    extra_kernel_params, driver_info persistence, extra serialization, and
    the returned tuple.
  • _prettify_for_display — JSON parsing in extra, non-JSON left
    untouched, deep copy with/without extra.

Mocking notes

  • Module-level collaborators (deep_decrypt, deep_merge, get_vault,
    get_device_oob_ip, SUPPORTED_IPA_TYPES, _derive_as_from_hostname_yrzn,
    _get_metalbox_primary_ip4) are patched at osism.tasks.conductor.ironic.*.
  • The NetBox client is patched via osism.utils.nb (with create=True), which
    is what the helpers' local from osism import utils resolves to — matching
    the existing test_netbox.py and sonic test fixtures. ironic.py has no
    module-level utils attribute (it imports it as osism_utils), so the
    literal osism.tasks.conductor.ironic.utils.nb path from the issue is not a
    valid patch target; osism.utils.nb is the established equivalent.
  • deep_decrypt / deep_merge are stubbed as no-ops; their real behavior is
    covered by the conductor.utils tests, so the merge cases assert call
    identity/order rather than merged output.

Deviation from the issue

The issue lists _derive_as_from_hostname_yrzn("comp-nw-22-3-7-1")
"4200143307". The implementation pads rack/server and produces
"4200140703" (matches the function's own docstring example
stor-nw-22-60-59-64200155960). The test encodes the verified value
"4200140703"; the same case (non-storage type 4, single-digit padding) is
still covered.

Verification

  • black --check, flake8 — pass on the new file.
  • pytest / coverage — left to the python-osism-unit-tests Zuul job.

AI-assisted: Claude Code

@berendt berendt force-pushed the implement/issue-2226-ironic-helper-tests branch from 75a2e60 to 1ce607c Compare June 10, 2026 20:49
@berendt berendt marked this pull request as ready for review June 10, 2026 20:55
@berendt berendt requested a review from ideaship June 10, 2026 20:55

@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 left some high level feedback:

  • The local _YRZN_MAPPING test fixture duplicates the SUPPORTED_IPA_TYPES structure and can silently diverge from the real mapping over time; consider deriving it from SUPPORTED_IPA_TYPES at runtime (e.g. SUPPORTED_IPA_TYPES['yrzn001'].copy() or similar) and only overriding what needs to be controlled for the tests.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The local `_YRZN_MAPPING` test fixture duplicates the `SUPPORTED_IPA_TYPES` structure and can silently diverge from the real mapping over time; consider deriving it from `SUPPORTED_IPA_TYPES` at runtime (e.g. `SUPPORTED_IPA_TYPES['yrzn001'].copy()` or similar) and only overriding what needs to be controlled for the tests.

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.

@ideaship ideaship left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Production bug exposed by the coverage gap (osism/tasks/conductor/utils.py)

_prepare_node_attributes() returns a 2-tuple (node_attributes, template_vars) (ironic.py:332). Two callers in utils.py still treat the return value as a dict:

  • _get_conductor_redfish_credentials (utils.py:223, then utils.py:227)
  • _get_conductor_redfish_address (utils.py:255, then utils.py:259)

Both do node_attributes = _prepare_node_attributes(...) and then if "driver_info" in node_attributes and node_attributes.get("driver") == "redfish":. Because node_attributes is now a tuple, "driver_info" in node_attributes is a tuple-membership test — is the string equal to one of the two dict elements? — which is always False. The if short-circuits before .get() is ever reached, so no exception is raised and nothing is logged: both functions silently return None, None / None regardless of the device's actual configuration.

What this actually breaks. These two helpers are the conductor-configuration fallback for Redfish, and they are the only source of credentials for the osism redfish list <hostname> <EthernetInterfaces|NetworkAdapters|NetworkDeviceFunctions> command. All three internal call sites call get_redfish_connection(hostname, ignore_ssl_errors=True) with no username/password, so:

  • if not username or not password: is always true → _get_conductor_redfish_credentials is invoked on every call to that command, and get_redfish_connection never reads credentials from Ironic driver_info (it only pulls redfish_address from there). With the bug, credentials always come back None, None, the BMC connection is built with SessionOrBasicAuth(username=None, password=None), and against any BMC that requires authentication the Redfish call fails, the exception is swallowed in redfish.py, and the command returns an empty list / "Could not establish Redfish connection" — with no hint that credentials were dropped. In practice the conductor-credential fallback is dead.
  • The address fallback (_get_conductor_redfish_address) likewise returns None, so base_url silently stays at the default https://{hostname} instead of the conductor-configured address. Lower impact (it degrades rather than hard-fails), but still wrong.

The tuple return was introduced in 0e66cb8, which changed ironic.py but never updated these two utils.py callers; test_utils.py has zero coverage of either function, so nothing in the suite catches it. This test-only PR cannot catch it by design — it never exercises the utils.py consumers — so flagging it for a separate fix.

Fix: unpack at both call sites — node_attributes, _ = _prepare_node_attributes(...) — and add a regression test for _get_conductor_redfish_credentials and _get_conductor_redfish_address.

Comment thread tests/unit/tasks/conductor/test_ironic_helpers.py
Comment thread tests/unit/tasks/conductor/test_ironic_helpers.py
Comment thread tests/unit/tasks/conductor/test_ironic_helpers.py
@github-project-automation github-project-automation Bot moved this from Ready to In review in Human Board Jun 23, 2026
@berendt berendt force-pushed the implement/issue-2226-ironic-helper-tests branch from 1ce607c to edbf47d Compare July 2, 2026 14:07
@berendt berendt requested a review from ideaship July 2, 2026 14:12
@berendt berendt force-pushed the implement/issue-2226-ironic-helper-tests branch from edbf47d to 8a115d0 Compare July 2, 2026 14:13
Cover the helpers in osism/tasks/conductor/ironic.py that can be
tested without the full sync orchestration (issue #2226):

- _derive_as_from_hostname_yrzn
- _get_metalbox_primary_ip4_fallback
- _get_metalbox_primary_ip4
- _render_templates
- _prepare_node_attributes
- _prettify_for_display

Collaborators are patched at their ironic.py import sites. The NetBox
client is replaced via osism.utils.nb, which the helpers' local
"from osism import utils" resolves to, matching the existing
test_netbox and sonic test conventions.

The ironic_parameters merge wiring is additionally checked with the
real deep_merge, asserting the returned attributes contain both the
base and the config-context overlay keys. A "secrets" custom field
that is present but explicitly null in NetBox is covered as its own
case, exercising the None guard in _prepare_node_attributes.

Note: the issue's example asserting
_derive_as_from_hostname_yrzn("comp-nw-22-3-7-1") == "4200143307"
does not match the implementation, which pads rack/server and yields
"4200140703" (consistent with the function's own docstring example).
The test encodes the verified value.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Christian Berendt <berendt@osism.tech>
@berendt berendt force-pushed the implement/issue-2226-ironic-helper-tests branch from 8a115d0 to 815dbe5 Compare July 2, 2026 14:15
@ideaship ideaship merged commit e361620 into main Jul 2, 2026
3 checks passed
@ideaship ideaship deleted the implement/issue-2226-ironic-helper-tests branch July 2, 2026 17:59
@github-project-automation github-project-automation Bot moved this from In review to Done in Human Board Jul 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Unit tests for osism/tasks/conductor/ironic.py — pure helpers

3 participants