Skip to content

Unit tests for osism/utils/rabbitmq.py #2232

@berendt

Description

@berendt

Background

Follow-up to #2192 (foundation) and PR #2193 (pytest + Zuul infrastructure). Part of Tier 4 (#2199). osism/utils/rabbitmq.py (173 LOC) resolves RabbitMQ node IPs by combining ansible-inventory calls, ansible-facts cached in Redis, and Jinja2 template resolution from internal_interface. Includes a small helper for loading the encrypted RabbitMQ password.

Scope

Add tests/unit/utils/test_rabbitmq.py covering both functions in osism/utils/rabbitmq.py.

Test targets

get_rabbitmq_node_addresses()rabbitmq.py:13

Patch:

  • osism.utils.rabbitmq.subprocess.check_output
  • osism.utils.rabbitmq.get_inventory_path
  • osism.utils.rabbitmq.get_hosts_from_inventory
  • osism.utils.rabbitmq.utils.redis (the lazy attribute on osism.utils) — easiest via mocker.patch("osism.utils.rabbitmq.utils.redis", ...) to a MagicMock

Inventory & host discovery

  • Inventory call returns valid JSON, two hosts → both processed in alphabetical order (the function calls .sort() on the host list)
  • get_hosts_from_inventory returns [] → returns None, error logged "No hosts found in rabbitmq group"
  • subprocess.check_output (first call, group listing) raises CalledProcessError → returns None, error logged
  • First call returns invalid JSON → returns None (json.JSONDecodeError), error logged
  • Outer generic exception → returns None, error logged

Per-host resolution

  • redis.get("ansible_facts<host>") returns None → host skipped, error logged, processing continues to next host
  • Hostvars query returns no internal_interface → host skipped, error logged
  • internal_interface is a literal string ("eth0") → used directly without templating
  • internal_interface is a Jinja template ("{{ ansible_local.testbed_network_devices.management }}") → resolved against the facts dict via dotted path traversal
  • Template path resolves to a non-string (None, dict, int) → host skipped, error logged
  • Template path traversal hits a non-dict mid-walk (e.g. ansible_local.foo is a string) → resolves to None, host skipped
  • Interface name normalization: "eth0.100""ansible_eth0_100", "eth-0""ansible_eth_0"
  • Facts missing the normalized interface key → host skipped, error logged
  • Interface facts present but no ipv4 key → host skipped, error logged
  • ipv4 present but address empty/missing → host skipped, error logged
  • Happy path: returns [("10.0.0.5", "host1"), ("10.0.0.6", "host2")] in alphabetical-host order

Aggregate results

  • All hosts skipped → returns None, error logged "Could not retrieve address for any RabbitMQ node"
  • At least one address → returns the list (no error)

load_rabbitmq_password()rabbitmq.py:139

Patch os.path.exists and osism.tasks.conductor.utils.load_yaml_file (or import path used by the production code: from osism.tasks.conductor.utils import load_yaml_file — patch at the call site).

  • File missing → returns None, error logged
  • load_yaml_file returns None → returns None, error logged "Empty or invalid secrets file"
  • load_yaml_file returns a non-dict (e.g. a list) → returns None, error logged
  • Dict without rabbitmq_password key → returns None, error logged
  • Dict with rabbitmq_password=" hunter2 " → returns "hunter2" (stripped)
  • rabbitmq_password=42 (int) → coerced via str(...).strip()"42"
  • load_yaml_file raises → returns None, error logged

Mocking hints

  • The function makes two subprocess.check_output calls per host (one for the group, one for the host vars). Use side_effect with a list of payloads:
    mocker.patch("osism.utils.rabbitmq.subprocess.check_output", side_effect=[
        json.dumps({"rabbitmq": {"hosts": ["host1", "host2"]}, ...}).encode(),
        json.dumps({"internal_interface": "eth0"}).encode(),
        json.dumps({"internal_interface": "eth0"}).encode(),
    ])
  • redis.get is a MagicMock; set its side_effect per host or use .return_value=json.dumps(facts).encode().
  • For the Jinja template path, build facts as nested dicts:
    facts = {"ansible_local": {"testbed_network_devices": {"management": "eth1"}}}
  • get_inventory_path returns a string path; the function is covered in Unit tests for osism/utils/inventory.py #2194, so just stub it here.
  • RABBITMQ_USER is a module constant — verify its value ("openstack") in one quick test.

Definition of Done

  • tests/unit/utils/test_rabbitmq.py created
  • All listed cases covered
  • pytest --cov=osism.utils.rabbitmq shows ≥ 95 %
  • pipenv run pytest tests/unit/utils/test_rabbitmq.py passes locally
  • flake8, mypy, python-black remain green
  • Zuul job python-osism-unit-tests passes

Dependencies

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions