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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions