all:
  children:
    zuul_unreachable:
      hosts: {}
  hosts:
    debian-bookworm:
      ansible_connection: ssh
      ansible_host: 2a13:1a81:8000:3124:f816:3eff:fe5b:3a02
      ansible_port: 22
      ansible_python_interpreter: auto
      ansible_user: zuul
      nodepool:
        az: nova
        cloud: regiocloud-a-ci
        external_id: b2af3930-f6de-47f7-8297-d9af43b7d9c1
        host_id: 5afd8fc909abddfe2ce3a956a91bbe56629648b7209fd0acfa541862
        interface_ip: 2a13:1a81:8000:3124:f816:3eff:fe5b:3a02
        label: debian-bookworm
        node_properties: {}
        private_ipv4: 10.128.0.109
        private_ipv6: null
        provider: regiocloud-a
        public_ipv4: 81.163.192.69
        public_ipv6: 2a13:1a81:8000:3124:f816:3eff:fe5b:3a02
        region: null
        slot: null
  vars:
    zuul:
      _inheritance_path:
      - '<Job base explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/zuul-config/zuul.d/jobs.yaml@main#2>'
      - '<Job python-osism-integration-tests explicit: None implied: {MatchAny:{ImpliedBranchMatcher:main}}
        source: osism/python-osism/.zuul.yaml@main#138>'
      - '<Job python-osism-integration-tests explicit: None implied: None source:
        osism/python-osism/.zuul.yaml@main#143>'
      ansible_version: '9'
      attempts: 1
      branch: main
      build: 114a35cb2bbf4429ae8745104466bd61
      build_refs:
      - branch: main
        change: '2460'
        change_message: "Add unit tests for osism/services websocket_manager and event_bridge\n\nCloses
          #2357.\n\nCreates the `tests/unit/services/` package with unit tests for
          `osism/services/websocket_manager.py` and `osism/services/event_bridge.py`,
          which previously had no coverage.\n\nWriting and reviewing the tests surfaced
          three production defects in `osism/services/event_bridge.py`; they are fixed
          in separate commits on top of the test commit.\n\n## Production fixes\n\n-
          **Close failed Redis subscriber before reconnect creates a new one**: when
          `subscribe()` raised, `_init_redis()` replaced `self._redis_subscriber`
          without closing the failed instance, and the per-iteration `finally` then
          closed the fresh replacement \u2014 the failed subscriber leaked and the
          next attempt subscribed on an already closed object. The failed subscriber
          is now closed before re-initialization, and the loop closes the current
          subscriber exactly once on exit.\n- **Route `get_message` failures through
          subscriber back-off**: a failing `get_message()` resubscribed immediately
          on the closed subscriber and bypassed the retry counter and `_shutdown_event.wait()`
          back-off entirely, so persistent failures busy-looped forever. The error
          is now re-raised into the same bounded back-off path as a failing `subscribe()`,
          which closes the failed subscriber and resubscribes on a fresh one from
          `_init_redis()`.\n- **Marshal bridged events onto the broadcaster event
          loop**: `_process_single_event()` drove `broadcast_event_from_notification()`
          on a freshly created event loop in a worker thread, waking the broadcaster's
          `asyncio.Queue` waiters on the API loop in a non-thread-safe way (unreliable
          delivery, error swallowed). `WebSocketManager` now records the loop its
          broadcaster runs on, and the bridge submits the coroutine with `asyncio.run_coroutine_threadsafe()`;
          the private-loop path remains only as a fallback while no broadcaster loop
          exists yet (the queue has no waiters then, so it is safe).\n\n## test_websocket_manager.py\n\n-
          `EventMessage`: constructor attributes, UUID4 id uniqueness, ISO 8601 `Z`-suffixed
          timestamp, exact `to_dict()` keys, `to_json()` round trip\n- `WebSocketConnection.matches_filters()`:
          no-filter pass-through, event/node/service filters incl. `node_name=None`
          rejection, `\"\"` \u2192 `unknown` service mapping and AND-combination of
          filters\n- `WebSocketManager.connect()`/`disconnect()`: accept + registration,
          broadcaster started only once while running (`done()` check), restart after
          completion, no-op disconnect for unknown websockets\n- `update_filters()`:
          full and partial updates, clearing via `[]`, unknown websocket no-op\n-
          `add_event()`/`send_heartbeat()`: queueing, early return without connections,
          heartbeat event shape\n- `broadcast_event_from_notification()`: identifier
          extraction for baremetal/compute/nova/network/neutron/volume/image/identity
          payloads incl. fallback keys, missing payload keys, unknown service types,
          empty event type, payload copy semantics and the swallowed-exception path\n-
          `_broadcast_events()`: filtered delivery, `WebSocketDisconnect` and generic-error
          connection cleanup, consumption without connections and clean cancellation\n\n##
          test_event_bridge.py\n\n- `_init_redis()`: default connection parameters,
          `REDIS_HOST`/`REDIS_PORT`/`REDIS_DB` overrides, ping failure handling and
          the `REDIS_AVAILABLE = False` local-queue-only path\n- `set_websocket_manager()`:
          thread startup guards for subscriber and processor threads\n- `add_event()`:
          publish payload, zero-subscriber warning, reconnect-then-publish, fallback
          to the local queue when reconnection fails, `queue.Full` (documented as
          a defensive branch unreachable with the unbounded queue) and generic error
          swallowing\n- `_redis_subscriber_loop()`: missing subscriber, subscribe/get_message
          flow, message dispatch with and without a WebSocket manager, invalid JSON,
          processing errors; `subscribe()` and `get_message()` failures both run through
          the bounded back-off \u2014 the tests install successive distinct subscriber
          mocks via the `_init_redis()` side effect and assert that each failed subscriber
          is closed and each retry subscribes on the newly created instance; retry
          exhaustion and swallowed `close()` errors\n- `_process_single_event()`:
          missing-manager warning, `AsyncMock` broadcast dispatch and swallowed coroutine
          errors, plus an integration-style test that delivers an event from a worker
          thread through a real `WebSocketManager` with an active broadcaster to a
          connected client\n- `_process_events()`: queued event processing with `task_done()`,
          empty-queue continuation, pre-set shutdown exit and error continuation\n-
          `shutdown()`: shutdown event, subscriber close incl. error path and conditional
          thread joins\n\n## Notes\n\n- All tests use fresh `WebSocketManager`/`EventBridge`
          instances instead of the module-level singletons; `EventBridge` is always
          constructed with `redis.Redis` patched.\n- The thread loops are called synchronously
          and terminated via `_shutdown_event`-driven side effects; the asyncio broadcaster
          tests bound the task with cancellation in `finally` and carry `pytest-timeout`
          markers, so no test can hang CI.\n- `pytest-asyncio` 1.4.0 is added to the
          Pipfile `[dev-packages]` (compatible with pytest 9, `pytest<10,>=8.4`);
          async tests are marked explicitly with `@pytest.mark.asyncio`, so `--strict-markers`
          stays happy.\n\n\U0001F916 Generated with [Claude Code](https://claude.com/claude-code)\n"
        change_url: https://github.com/osism/python-osism/pull/2460
        commit_id: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        patchset: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/python-osism
          name: osism/python-osism
          short_name: python-osism
        src_dir: src/github.com/osism/python-osism
        topic: null
      buildset: aafa81d6b2524452b94f302e1099c88a
      buildset_refs:
      - branch: main
        change: '2460'
        change_message: "Add unit tests for osism/services websocket_manager and event_bridge\n\nCloses
          #2357.\n\nCreates the `tests/unit/services/` package with unit tests for
          `osism/services/websocket_manager.py` and `osism/services/event_bridge.py`,
          which previously had no coverage.\n\nWriting and reviewing the tests surfaced
          three production defects in `osism/services/event_bridge.py`; they are fixed
          in separate commits on top of the test commit.\n\n## Production fixes\n\n-
          **Close failed Redis subscriber before reconnect creates a new one**: when
          `subscribe()` raised, `_init_redis()` replaced `self._redis_subscriber`
          without closing the failed instance, and the per-iteration `finally` then
          closed the fresh replacement \u2014 the failed subscriber leaked and the
          next attempt subscribed on an already closed object. The failed subscriber
          is now closed before re-initialization, and the loop closes the current
          subscriber exactly once on exit.\n- **Route `get_message` failures through
          subscriber back-off**: a failing `get_message()` resubscribed immediately
          on the closed subscriber and bypassed the retry counter and `_shutdown_event.wait()`
          back-off entirely, so persistent failures busy-looped forever. The error
          is now re-raised into the same bounded back-off path as a failing `subscribe()`,
          which closes the failed subscriber and resubscribes on a fresh one from
          `_init_redis()`.\n- **Marshal bridged events onto the broadcaster event
          loop**: `_process_single_event()` drove `broadcast_event_from_notification()`
          on a freshly created event loop in a worker thread, waking the broadcaster's
          `asyncio.Queue` waiters on the API loop in a non-thread-safe way (unreliable
          delivery, error swallowed). `WebSocketManager` now records the loop its
          broadcaster runs on, and the bridge submits the coroutine with `asyncio.run_coroutine_threadsafe()`;
          the private-loop path remains only as a fallback while no broadcaster loop
          exists yet (the queue has no waiters then, so it is safe).\n\n## test_websocket_manager.py\n\n-
          `EventMessage`: constructor attributes, UUID4 id uniqueness, ISO 8601 `Z`-suffixed
          timestamp, exact `to_dict()` keys, `to_json()` round trip\n- `WebSocketConnection.matches_filters()`:
          no-filter pass-through, event/node/service filters incl. `node_name=None`
          rejection, `\"\"` \u2192 `unknown` service mapping and AND-combination of
          filters\n- `WebSocketManager.connect()`/`disconnect()`: accept + registration,
          broadcaster started only once while running (`done()` check), restart after
          completion, no-op disconnect for unknown websockets\n- `update_filters()`:
          full and partial updates, clearing via `[]`, unknown websocket no-op\n-
          `add_event()`/`send_heartbeat()`: queueing, early return without connections,
          heartbeat event shape\n- `broadcast_event_from_notification()`: identifier
          extraction for baremetal/compute/nova/network/neutron/volume/image/identity
          payloads incl. fallback keys, missing payload keys, unknown service types,
          empty event type, payload copy semantics and the swallowed-exception path\n-
          `_broadcast_events()`: filtered delivery, `WebSocketDisconnect` and generic-error
          connection cleanup, consumption without connections and clean cancellation\n\n##
          test_event_bridge.py\n\n- `_init_redis()`: default connection parameters,
          `REDIS_HOST`/`REDIS_PORT`/`REDIS_DB` overrides, ping failure handling and
          the `REDIS_AVAILABLE = False` local-queue-only path\n- `set_websocket_manager()`:
          thread startup guards for subscriber and processor threads\n- `add_event()`:
          publish payload, zero-subscriber warning, reconnect-then-publish, fallback
          to the local queue when reconnection fails, `queue.Full` (documented as
          a defensive branch unreachable with the unbounded queue) and generic error
          swallowing\n- `_redis_subscriber_loop()`: missing subscriber, subscribe/get_message
          flow, message dispatch with and without a WebSocket manager, invalid JSON,
          processing errors; `subscribe()` and `get_message()` failures both run through
          the bounded back-off \u2014 the tests install successive distinct subscriber
          mocks via the `_init_redis()` side effect and assert that each failed subscriber
          is closed and each retry subscribes on the newly created instance; retry
          exhaustion and swallowed `close()` errors\n- `_process_single_event()`:
          missing-manager warning, `AsyncMock` broadcast dispatch and swallowed coroutine
          errors, plus an integration-style test that delivers an event from a worker
          thread through a real `WebSocketManager` with an active broadcaster to a
          connected client\n- `_process_events()`: queued event processing with `task_done()`,
          empty-queue continuation, pre-set shutdown exit and error continuation\n-
          `shutdown()`: shutdown event, subscriber close incl. error path and conditional
          thread joins\n\n## Notes\n\n- All tests use fresh `WebSocketManager`/`EventBridge`
          instances instead of the module-level singletons; `EventBridge` is always
          constructed with `redis.Redis` patched.\n- The thread loops are called synchronously
          and terminated via `_shutdown_event`-driven side effects; the asyncio broadcaster
          tests bound the task with cancellation in `finally` and carry `pytest-timeout`
          markers, so no test can hang CI.\n- `pytest-asyncio` 1.4.0 is added to the
          Pipfile `[dev-packages]` (compatible with pytest 9, `pytest<10,>=8.4`);
          async tests are marked explicitly with `@pytest.mark.asyncio`, so `--strict-markers`
          stays happy.\n\n\U0001F916 Generated with [Claude Code](https://claude.com/claude-code)\n"
        change_url: https://github.com/osism/python-osism/pull/2460
        commit_id: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        patchset: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/python-osism
          name: osism/python-osism
          short_name: python-osism
        src_dir: src/github.com/osism/python-osism
        topic: null
      change: '2460'
      change_message: "Add unit tests for osism/services websocket_manager and event_bridge\n\nCloses
        #2357.\n\nCreates the `tests/unit/services/` package with unit tests for `osism/services/websocket_manager.py`
        and `osism/services/event_bridge.py`, which previously had no coverage.\n\nWriting
        and reviewing the tests surfaced three production defects in `osism/services/event_bridge.py`;
        they are fixed in separate commits on top of the test commit.\n\n## Production
        fixes\n\n- **Close failed Redis subscriber before reconnect creates a new
        one**: when `subscribe()` raised, `_init_redis()` replaced `self._redis_subscriber`
        without closing the failed instance, and the per-iteration `finally` then
        closed the fresh replacement \u2014 the failed subscriber leaked and the next
        attempt subscribed on an already closed object. The failed subscriber is now
        closed before re-initialization, and the loop closes the current subscriber
        exactly once on exit.\n- **Route `get_message` failures through subscriber
        back-off**: a failing `get_message()` resubscribed immediately on the closed
        subscriber and bypassed the retry counter and `_shutdown_event.wait()` back-off
        entirely, so persistent failures busy-looped forever. The error is now re-raised
        into the same bounded back-off path as a failing `subscribe()`, which closes
        the failed subscriber and resubscribes on a fresh one from `_init_redis()`.\n-
        **Marshal bridged events onto the broadcaster event loop**: `_process_single_event()`
        drove `broadcast_event_from_notification()` on a freshly created event loop
        in a worker thread, waking the broadcaster's `asyncio.Queue` waiters on the
        API loop in a non-thread-safe way (unreliable delivery, error swallowed).
        `WebSocketManager` now records the loop its broadcaster runs on, and the bridge
        submits the coroutine with `asyncio.run_coroutine_threadsafe()`; the private-loop
        path remains only as a fallback while no broadcaster loop exists yet (the
        queue has no waiters then, so it is safe).\n\n## test_websocket_manager.py\n\n-
        `EventMessage`: constructor attributes, UUID4 id uniqueness, ISO 8601 `Z`-suffixed
        timestamp, exact `to_dict()` keys, `to_json()` round trip\n- `WebSocketConnection.matches_filters()`:
        no-filter pass-through, event/node/service filters incl. `node_name=None`
        rejection, `\"\"` \u2192 `unknown` service mapping and AND-combination of
        filters\n- `WebSocketManager.connect()`/`disconnect()`: accept + registration,
        broadcaster started only once while running (`done()` check), restart after
        completion, no-op disconnect for unknown websockets\n- `update_filters()`:
        full and partial updates, clearing via `[]`, unknown websocket no-op\n- `add_event()`/`send_heartbeat()`:
        queueing, early return without connections, heartbeat event shape\n- `broadcast_event_from_notification()`:
        identifier extraction for baremetal/compute/nova/network/neutron/volume/image/identity
        payloads incl. fallback keys, missing payload keys, unknown service types,
        empty event type, payload copy semantics and the swallowed-exception path\n-
        `_broadcast_events()`: filtered delivery, `WebSocketDisconnect` and generic-error
        connection cleanup, consumption without connections and clean cancellation\n\n##
        test_event_bridge.py\n\n- `_init_redis()`: default connection parameters,
        `REDIS_HOST`/`REDIS_PORT`/`REDIS_DB` overrides, ping failure handling and
        the `REDIS_AVAILABLE = False` local-queue-only path\n- `set_websocket_manager()`:
        thread startup guards for subscriber and processor threads\n- `add_event()`:
        publish payload, zero-subscriber warning, reconnect-then-publish, fallback
        to the local queue when reconnection fails, `queue.Full` (documented as a
        defensive branch unreachable with the unbounded queue) and generic error swallowing\n-
        `_redis_subscriber_loop()`: missing subscriber, subscribe/get_message flow,
        message dispatch with and without a WebSocket manager, invalid JSON, processing
        errors; `subscribe()` and `get_message()` failures both run through the bounded
        back-off \u2014 the tests install successive distinct subscriber mocks via
        the `_init_redis()` side effect and assert that each failed subscriber is
        closed and each retry subscribes on the newly created instance; retry exhaustion
        and swallowed `close()` errors\n- `_process_single_event()`: missing-manager
        warning, `AsyncMock` broadcast dispatch and swallowed coroutine errors, plus
        an integration-style test that delivers an event from a worker thread through
        a real `WebSocketManager` with an active broadcaster to a connected client\n-
        `_process_events()`: queued event processing with `task_done()`, empty-queue
        continuation, pre-set shutdown exit and error continuation\n- `shutdown()`:
        shutdown event, subscriber close incl. error path and conditional thread joins\n\n##
        Notes\n\n- All tests use fresh `WebSocketManager`/`EventBridge` instances
        instead of the module-level singletons; `EventBridge` is always constructed
        with `redis.Redis` patched.\n- The thread loops are called synchronously and
        terminated via `_shutdown_event`-driven side effects; the asyncio broadcaster
        tests bound the task with cancellation in `finally` and carry `pytest-timeout`
        markers, so no test can hang CI.\n- `pytest-asyncio` 1.4.0 is added to the
        Pipfile `[dev-packages]` (compatible with pytest 9, `pytest<10,>=8.4`); async
        tests are marked explicitly with `@pytest.mark.asyncio`, so `--strict-markers`
        stays happy.\n\n\U0001F916 Generated with [Claude Code](https://claude.com/claude-code)\n"
      change_url: https://github.com/osism/python-osism/pull/2460
      child_jobs: []
      commit_id: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
      event_id: 5843a490-865b-11f1-9640-3c30d9760c44
      executor:
        hostname: 521e9411259a
        inventory_file: /var/lib/zuul/builds/114a35cb2bbf4429ae8745104466bd61/ansible/inventory.yaml
        log_root: /var/lib/zuul/builds/114a35cb2bbf4429ae8745104466bd61/work/logs
        result_data_file: /var/lib/zuul/builds/114a35cb2bbf4429ae8745104466bd61/work/results.json
        src_root: /var/lib/zuul/builds/114a35cb2bbf4429ae8745104466bd61/work/src
        work_root: /var/lib/zuul/builds/114a35cb2bbf4429ae8745104466bd61/work
      include_vars: []
      items:
      - branch: main
        change: '2460'
        change_message: "Add unit tests for osism/services websocket_manager and event_bridge\n\nCloses
          #2357.\n\nCreates the `tests/unit/services/` package with unit tests for
          `osism/services/websocket_manager.py` and `osism/services/event_bridge.py`,
          which previously had no coverage.\n\nWriting and reviewing the tests surfaced
          three production defects in `osism/services/event_bridge.py`; they are fixed
          in separate commits on top of the test commit.\n\n## Production fixes\n\n-
          **Close failed Redis subscriber before reconnect creates a new one**: when
          `subscribe()` raised, `_init_redis()` replaced `self._redis_subscriber`
          without closing the failed instance, and the per-iteration `finally` then
          closed the fresh replacement \u2014 the failed subscriber leaked and the
          next attempt subscribed on an already closed object. The failed subscriber
          is now closed before re-initialization, and the loop closes the current
          subscriber exactly once on exit.\n- **Route `get_message` failures through
          subscriber back-off**: a failing `get_message()` resubscribed immediately
          on the closed subscriber and bypassed the retry counter and `_shutdown_event.wait()`
          back-off entirely, so persistent failures busy-looped forever. The error
          is now re-raised into the same bounded back-off path as a failing `subscribe()`,
          which closes the failed subscriber and resubscribes on a fresh one from
          `_init_redis()`.\n- **Marshal bridged events onto the broadcaster event
          loop**: `_process_single_event()` drove `broadcast_event_from_notification()`
          on a freshly created event loop in a worker thread, waking the broadcaster's
          `asyncio.Queue` waiters on the API loop in a non-thread-safe way (unreliable
          delivery, error swallowed). `WebSocketManager` now records the loop its
          broadcaster runs on, and the bridge submits the coroutine with `asyncio.run_coroutine_threadsafe()`;
          the private-loop path remains only as a fallback while no broadcaster loop
          exists yet (the queue has no waiters then, so it is safe).\n\n## test_websocket_manager.py\n\n-
          `EventMessage`: constructor attributes, UUID4 id uniqueness, ISO 8601 `Z`-suffixed
          timestamp, exact `to_dict()` keys, `to_json()` round trip\n- `WebSocketConnection.matches_filters()`:
          no-filter pass-through, event/node/service filters incl. `node_name=None`
          rejection, `\"\"` \u2192 `unknown` service mapping and AND-combination of
          filters\n- `WebSocketManager.connect()`/`disconnect()`: accept + registration,
          broadcaster started only once while running (`done()` check), restart after
          completion, no-op disconnect for unknown websockets\n- `update_filters()`:
          full and partial updates, clearing via `[]`, unknown websocket no-op\n-
          `add_event()`/`send_heartbeat()`: queueing, early return without connections,
          heartbeat event shape\n- `broadcast_event_from_notification()`: identifier
          extraction for baremetal/compute/nova/network/neutron/volume/image/identity
          payloads incl. fallback keys, missing payload keys, unknown service types,
          empty event type, payload copy semantics and the swallowed-exception path\n-
          `_broadcast_events()`: filtered delivery, `WebSocketDisconnect` and generic-error
          connection cleanup, consumption without connections and clean cancellation\n\n##
          test_event_bridge.py\n\n- `_init_redis()`: default connection parameters,
          `REDIS_HOST`/`REDIS_PORT`/`REDIS_DB` overrides, ping failure handling and
          the `REDIS_AVAILABLE = False` local-queue-only path\n- `set_websocket_manager()`:
          thread startup guards for subscriber and processor threads\n- `add_event()`:
          publish payload, zero-subscriber warning, reconnect-then-publish, fallback
          to the local queue when reconnection fails, `queue.Full` (documented as
          a defensive branch unreachable with the unbounded queue) and generic error
          swallowing\n- `_redis_subscriber_loop()`: missing subscriber, subscribe/get_message
          flow, message dispatch with and without a WebSocket manager, invalid JSON,
          processing errors; `subscribe()` and `get_message()` failures both run through
          the bounded back-off \u2014 the tests install successive distinct subscriber
          mocks via the `_init_redis()` side effect and assert that each failed subscriber
          is closed and each retry subscribes on the newly created instance; retry
          exhaustion and swallowed `close()` errors\n- `_process_single_event()`:
          missing-manager warning, `AsyncMock` broadcast dispatch and swallowed coroutine
          errors, plus an integration-style test that delivers an event from a worker
          thread through a real `WebSocketManager` with an active broadcaster to a
          connected client\n- `_process_events()`: queued event processing with `task_done()`,
          empty-queue continuation, pre-set shutdown exit and error continuation\n-
          `shutdown()`: shutdown event, subscriber close incl. error path and conditional
          thread joins\n\n## Notes\n\n- All tests use fresh `WebSocketManager`/`EventBridge`
          instances instead of the module-level singletons; `EventBridge` is always
          constructed with `redis.Redis` patched.\n- The thread loops are called synchronously
          and terminated via `_shutdown_event`-driven side effects; the asyncio broadcaster
          tests bound the task with cancellation in `finally` and carry `pytest-timeout`
          markers, so no test can hang CI.\n- `pytest-asyncio` 1.4.0 is added to the
          Pipfile `[dev-packages]` (compatible with pytest 9, `pytest<10,>=8.4`);
          async tests are marked explicitly with `@pytest.mark.asyncio`, so `--strict-markers`
          stays happy.\n\n\U0001F916 Generated with [Claude Code](https://claude.com/claude-code)\n"
        change_url: https://github.com/osism/python-osism/pull/2460
        commit_id: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        patchset: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
        project:
          canonical_hostname: github.com
          canonical_name: github.com/osism/python-osism
          name: osism/python-osism
          short_name: python-osism
          src_dir: src/github.com/osism/python-osism
        topic: null
      job: python-osism-integration-tests
      jobtags: []
      max_attempts: 3
      message: QWRkIHVuaXQgdGVzdHMgZm9yIG9zaXNtL3NlcnZpY2VzIHdlYnNvY2tldF9tYW5hZ2VyIGFuZCBldmVudF9icmlkZ2UKCkNsb3NlcyAjMjM1Ny4KCkNyZWF0ZXMgdGhlIGB0ZXN0cy91bml0L3NlcnZpY2VzL2AgcGFja2FnZSB3aXRoIHVuaXQgdGVzdHMgZm9yIGBvc2lzbS9zZXJ2aWNlcy93ZWJzb2NrZXRfbWFuYWdlci5weWAgYW5kIGBvc2lzbS9zZXJ2aWNlcy9ldmVudF9icmlkZ2UucHlgLCB3aGljaCBwcmV2aW91c2x5IGhhZCBubyBjb3ZlcmFnZS4KCldyaXRpbmcgYW5kIHJldmlld2luZyB0aGUgdGVzdHMgc3VyZmFjZWQgdGhyZWUgcHJvZHVjdGlvbiBkZWZlY3RzIGluIGBvc2lzbS9zZXJ2aWNlcy9ldmVudF9icmlkZ2UucHlgOyB0aGV5IGFyZSBmaXhlZCBpbiBzZXBhcmF0ZSBjb21taXRzIG9uIHRvcCBvZiB0aGUgdGVzdCBjb21taXQuCgojIyBQcm9kdWN0aW9uIGZpeGVzCgotICoqQ2xvc2UgZmFpbGVkIFJlZGlzIHN1YnNjcmliZXIgYmVmb3JlIHJlY29ubmVjdCBjcmVhdGVzIGEgbmV3IG9uZSoqOiB3aGVuIGBzdWJzY3JpYmUoKWAgcmFpc2VkLCBgX2luaXRfcmVkaXMoKWAgcmVwbGFjZWQgYHNlbGYuX3JlZGlzX3N1YnNjcmliZXJgIHdpdGhvdXQgY2xvc2luZyB0aGUgZmFpbGVkIGluc3RhbmNlLCBhbmQgdGhlIHBlci1pdGVyYXRpb24gYGZpbmFsbHlgIHRoZW4gY2xvc2VkIHRoZSBmcmVzaCByZXBsYWNlbWVudCDigJQgdGhlIGZhaWxlZCBzdWJzY3JpYmVyIGxlYWtlZCBhbmQgdGhlIG5leHQgYXR0ZW1wdCBzdWJzY3JpYmVkIG9uIGFuIGFscmVhZHkgY2xvc2VkIG9iamVjdC4gVGhlIGZhaWxlZCBzdWJzY3JpYmVyIGlzIG5vdyBjbG9zZWQgYmVmb3JlIHJlLWluaXRpYWxpemF0aW9uLCBhbmQgdGhlIGxvb3AgY2xvc2VzIHRoZSBjdXJyZW50IHN1YnNjcmliZXIgZXhhY3RseSBvbmNlIG9uIGV4aXQuCi0gKipSb3V0ZSBgZ2V0X21lc3NhZ2VgIGZhaWx1cmVzIHRocm91Z2ggc3Vic2NyaWJlciBiYWNrLW9mZioqOiBhIGZhaWxpbmcgYGdldF9tZXNzYWdlKClgIHJlc3Vic2NyaWJlZCBpbW1lZGlhdGVseSBvbiB0aGUgY2xvc2VkIHN1YnNjcmliZXIgYW5kIGJ5cGFzc2VkIHRoZSByZXRyeSBjb3VudGVyIGFuZCBgX3NodXRkb3duX2V2ZW50LndhaXQoKWAgYmFjay1vZmYgZW50aXJlbHksIHNvIHBlcnNpc3RlbnQgZmFpbHVyZXMgYnVzeS1sb29wZWQgZm9yZXZlci4gVGhlIGVycm9yIGlzIG5vdyByZS1yYWlzZWQgaW50byB0aGUgc2FtZSBib3VuZGVkIGJhY2stb2ZmIHBhdGggYXMgYSBmYWlsaW5nIGBzdWJzY3JpYmUoKWAsIHdoaWNoIGNsb3NlcyB0aGUgZmFpbGVkIHN1YnNjcmliZXIgYW5kIHJlc3Vic2NyaWJlcyBvbiBhIGZyZXNoIG9uZSBmcm9tIGBfaW5pdF9yZWRpcygpYC4KLSAqKk1hcnNoYWwgYnJpZGdlZCBldmVudHMgb250byB0aGUgYnJvYWRjYXN0ZXIgZXZlbnQgbG9vcCoqOiBgX3Byb2Nlc3Nfc2luZ2xlX2V2ZW50KClgIGRyb3ZlIGBicm9hZGNhc3RfZXZlbnRfZnJvbV9ub3RpZmljYXRpb24oKWAgb24gYSBmcmVzaGx5IGNyZWF0ZWQgZXZlbnQgbG9vcCBpbiBhIHdvcmtlciB0aHJlYWQsIHdha2luZyB0aGUgYnJvYWRjYXN0ZXIncyBgYXN5bmNpby5RdWV1ZWAgd2FpdGVycyBvbiB0aGUgQVBJIGxvb3AgaW4gYSBub24tdGhyZWFkLXNhZmUgd2F5ICh1bnJlbGlhYmxlIGRlbGl2ZXJ5LCBlcnJvciBzd2FsbG93ZWQpLiBgV2ViU29ja2V0TWFuYWdlcmAgbm93IHJlY29yZHMgdGhlIGxvb3AgaXRzIGJyb2FkY2FzdGVyIHJ1bnMgb24sIGFuZCB0aGUgYnJpZGdlIHN1Ym1pdHMgdGhlIGNvcm91dGluZSB3aXRoIGBhc3luY2lvLnJ1bl9jb3JvdXRpbmVfdGhyZWFkc2FmZSgpYDsgdGhlIHByaXZhdGUtbG9vcCBwYXRoIHJlbWFpbnMgb25seSBhcyBhIGZhbGxiYWNrIHdoaWxlIG5vIGJyb2FkY2FzdGVyIGxvb3AgZXhpc3RzIHlldCAodGhlIHF1ZXVlIGhhcyBubyB3YWl0ZXJzIHRoZW4sIHNvIGl0IGlzIHNhZmUpLgoKIyMgdGVzdF93ZWJzb2NrZXRfbWFuYWdlci5weQoKLSBgRXZlbnRNZXNzYWdlYDogY29uc3RydWN0b3IgYXR0cmlidXRlcywgVVVJRDQgaWQgdW5pcXVlbmVzcywgSVNPIDg2MDEgYFpgLXN1ZmZpeGVkIHRpbWVzdGFtcCwgZXhhY3QgYHRvX2RpY3QoKWAga2V5cywgYHRvX2pzb24oKWAgcm91bmQgdHJpcAotIGBXZWJTb2NrZXRDb25uZWN0aW9uLm1hdGNoZXNfZmlsdGVycygpYDogbm8tZmlsdGVyIHBhc3MtdGhyb3VnaCwgZXZlbnQvbm9kZS9zZXJ2aWNlIGZpbHRlcnMgaW5jbC4gYG5vZGVfbmFtZT1Ob25lYCByZWplY3Rpb24sIGAiImAg4oaSIGB1bmtub3duYCBzZXJ2aWNlIG1hcHBpbmcgYW5kIEFORC1jb21iaW5hdGlvbiBvZiBmaWx0ZXJzCi0gYFdlYlNvY2tldE1hbmFnZXIuY29ubmVjdCgpYC9gZGlzY29ubmVjdCgpYDogYWNjZXB0ICsgcmVnaXN0cmF0aW9uLCBicm9hZGNhc3RlciBzdGFydGVkIG9ubHkgb25jZSB3aGlsZSBydW5uaW5nIChgZG9uZSgpYCBjaGVjayksIHJlc3RhcnQgYWZ0ZXIgY29tcGxldGlvbiwgbm8tb3AgZGlzY29ubmVjdCBmb3IgdW5rbm93biB3ZWJzb2NrZXRzCi0gYHVwZGF0ZV9maWx0ZXJzKClgOiBmdWxsIGFuZCBwYXJ0aWFsIHVwZGF0ZXMsIGNsZWFyaW5nIHZpYSBgW11gLCB1bmtub3duIHdlYnNvY2tldCBuby1vcAotIGBhZGRfZXZlbnQoKWAvYHNlbmRfaGVhcnRiZWF0KClgOiBxdWV1ZWluZywgZWFybHkgcmV0dXJuIHdpdGhvdXQgY29ubmVjdGlvbnMsIGhlYXJ0YmVhdCBldmVudCBzaGFwZQotIGBicm9hZGNhc3RfZXZlbnRfZnJvbV9ub3RpZmljYXRpb24oKWA6IGlkZW50aWZpZXIgZXh0cmFjdGlvbiBmb3IgYmFyZW1ldGFsL2NvbXB1dGUvbm92YS9uZXR3b3JrL25ldXRyb24vdm9sdW1lL2ltYWdlL2lkZW50aXR5IHBheWxvYWRzIGluY2wuIGZhbGxiYWNrIGtleXMsIG1pc3NpbmcgcGF5bG9hZCBrZXlzLCB1bmtub3duIHNlcnZpY2UgdHlwZXMsIGVtcHR5IGV2ZW50IHR5cGUsIHBheWxvYWQgY29weSBzZW1hbnRpY3MgYW5kIHRoZSBzd2FsbG93ZWQtZXhjZXB0aW9uIHBhdGgKLSBgX2Jyb2FkY2FzdF9ldmVudHMoKWA6IGZpbHRlcmVkIGRlbGl2ZXJ5LCBgV2ViU29ja2V0RGlzY29ubmVjdGAgYW5kIGdlbmVyaWMtZXJyb3IgY29ubmVjdGlvbiBjbGVhbnVwLCBjb25zdW1wdGlvbiB3aXRob3V0IGNvbm5lY3Rpb25zIGFuZCBjbGVhbiBjYW5jZWxsYXRpb24KCiMjIHRlc3RfZXZlbnRfYnJpZGdlLnB5CgotIGBfaW5pdF9yZWRpcygpYDogZGVmYXVsdCBjb25uZWN0aW9uIHBhcmFtZXRlcnMsIGBSRURJU19IT1NUYC9gUkVESVNfUE9SVGAvYFJFRElTX0RCYCBvdmVycmlkZXMsIHBpbmcgZmFpbHVyZSBoYW5kbGluZyBhbmQgdGhlIGBSRURJU19BVkFJTEFCTEUgPSBGYWxzZWAgbG9jYWwtcXVldWUtb25seSBwYXRoCi0gYHNldF93ZWJzb2NrZXRfbWFuYWdlcigpYDogdGhyZWFkIHN0YXJ0dXAgZ3VhcmRzIGZvciBzdWJzY3JpYmVyIGFuZCBwcm9jZXNzb3IgdGhyZWFkcwotIGBhZGRfZXZlbnQoKWA6IHB1Ymxpc2ggcGF5bG9hZCwgemVyby1zdWJzY3JpYmVyIHdhcm5pbmcsIHJlY29ubmVjdC10aGVuLXB1Ymxpc2gsIGZhbGxiYWNrIHRvIHRoZSBsb2NhbCBxdWV1ZSB3aGVuIHJlY29ubmVjdGlvbiBmYWlscywgYHF1ZXVlLkZ1bGxgIChkb2N1bWVudGVkIGFzIGEgZGVmZW5zaXZlIGJyYW5jaCB1bnJlYWNoYWJsZSB3aXRoIHRoZSB1bmJvdW5kZWQgcXVldWUpIGFuZCBnZW5lcmljIGVycm9yIHN3YWxsb3dpbmcKLSBgX3JlZGlzX3N1YnNjcmliZXJfbG9vcCgpYDogbWlzc2luZyBzdWJzY3JpYmVyLCBzdWJzY3JpYmUvZ2V0X21lc3NhZ2UgZmxvdywgbWVzc2FnZSBkaXNwYXRjaCB3aXRoIGFuZCB3aXRob3V0IGEgV2ViU29ja2V0IG1hbmFnZXIsIGludmFsaWQgSlNPTiwgcHJvY2Vzc2luZyBlcnJvcnM7IGBzdWJzY3JpYmUoKWAgYW5kIGBnZXRfbWVzc2FnZSgpYCBmYWlsdXJlcyBib3RoIHJ1biB0aHJvdWdoIHRoZSBib3VuZGVkIGJhY2stb2ZmIOKAlCB0aGUgdGVzdHMgaW5zdGFsbCBzdWNjZXNzaXZlIGRpc3RpbmN0IHN1YnNjcmliZXIgbW9ja3MgdmlhIHRoZSBgX2luaXRfcmVkaXMoKWAgc2lkZSBlZmZlY3QgYW5kIGFzc2VydCB0aGF0IGVhY2ggZmFpbGVkIHN1YnNjcmliZXIgaXMgY2xvc2VkIGFuZCBlYWNoIHJldHJ5IHN1YnNjcmliZXMgb24gdGhlIG5ld2x5IGNyZWF0ZWQgaW5zdGFuY2U7IHJldHJ5IGV4aGF1c3Rpb24gYW5kIHN3YWxsb3dlZCBgY2xvc2UoKWAgZXJyb3JzCi0gYF9wcm9jZXNzX3NpbmdsZV9ldmVudCgpYDogbWlzc2luZy1tYW5hZ2VyIHdhcm5pbmcsIGBBc3luY01vY2tgIGJyb2FkY2FzdCBkaXNwYXRjaCBhbmQgc3dhbGxvd2VkIGNvcm91dGluZSBlcnJvcnMsIHBsdXMgYW4gaW50ZWdyYXRpb24tc3R5bGUgdGVzdCB0aGF0IGRlbGl2ZXJzIGFuIGV2ZW50IGZyb20gYSB3b3JrZXIgdGhyZWFkIHRocm91Z2ggYSByZWFsIGBXZWJTb2NrZXRNYW5hZ2VyYCB3aXRoIGFuIGFjdGl2ZSBicm9hZGNhc3RlciB0byBhIGNvbm5lY3RlZCBjbGllbnQKLSBgX3Byb2Nlc3NfZXZlbnRzKClgOiBxdWV1ZWQgZXZlbnQgcHJvY2Vzc2luZyB3aXRoIGB0YXNrX2RvbmUoKWAsIGVtcHR5LXF1ZXVlIGNvbnRpbnVhdGlvbiwgcHJlLXNldCBzaHV0ZG93biBleGl0IGFuZCBlcnJvciBjb250aW51YXRpb24KLSBgc2h1dGRvd24oKWA6IHNodXRkb3duIGV2ZW50LCBzdWJzY3JpYmVyIGNsb3NlIGluY2wuIGVycm9yIHBhdGggYW5kIGNvbmRpdGlvbmFsIHRocmVhZCBqb2lucwoKIyMgTm90ZXMKCi0gQWxsIHRlc3RzIHVzZSBmcmVzaCBgV2ViU29ja2V0TWFuYWdlcmAvYEV2ZW50QnJpZGdlYCBpbnN0YW5jZXMgaW5zdGVhZCBvZiB0aGUgbW9kdWxlLWxldmVsIHNpbmdsZXRvbnM7IGBFdmVudEJyaWRnZWAgaXMgYWx3YXlzIGNvbnN0cnVjdGVkIHdpdGggYHJlZGlzLlJlZGlzYCBwYXRjaGVkLgotIFRoZSB0aHJlYWQgbG9vcHMgYXJlIGNhbGxlZCBzeW5jaHJvbm91c2x5IGFuZCB0ZXJtaW5hdGVkIHZpYSBgX3NodXRkb3duX2V2ZW50YC1kcml2ZW4gc2lkZSBlZmZlY3RzOyB0aGUgYXN5bmNpbyBicm9hZGNhc3RlciB0ZXN0cyBib3VuZCB0aGUgdGFzayB3aXRoIGNhbmNlbGxhdGlvbiBpbiBgZmluYWxseWAgYW5kIGNhcnJ5IGBweXRlc3QtdGltZW91dGAgbWFya2Vycywgc28gbm8gdGVzdCBjYW4gaGFuZyBDSS4KLSBgcHl0ZXN0LWFzeW5jaW9gIDEuNC4wIGlzIGFkZGVkIHRvIHRoZSBQaXBmaWxlIGBbZGV2LXBhY2thZ2VzXWAgKGNvbXBhdGlibGUgd2l0aCBweXRlc3QgOSwgYHB5dGVzdDwxMCw+PTguNGApOyBhc3luYyB0ZXN0cyBhcmUgbWFya2VkIGV4cGxpY2l0bHkgd2l0aCBgQHB5dGVzdC5tYXJrLmFzeW5jaW9gLCBzbyBgLS1zdHJpY3QtbWFya2Vyc2Agc3RheXMgaGFwcHkuCgrwn6SWIEdlbmVyYXRlZCB3aXRoIFtDbGF1ZGUgQ29kZV0oaHR0cHM6Ly9jbGF1ZGUuY29tL2NsYXVkZS1jb2RlKQo=
      patchset: 46f6c45b4c8d1fe95e6aa39c0b87bd4ba40e9c27
      pipeline: check
      playbook_context:
        playbook_projects:
          trusted/project_0/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 71b711c422c10c242a8f785dc6d41ed9d12e9cd3
          trusted/project_1/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 242732586e2cba04d04867d3e302888c766bc231
          trusted/project_2/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 1d71e58c93442038e695a5d883d57a603182c7ae
          untrusted/project_0/github.com/osism/python-osism:
            canonical_name: github.com/osism/python-osism
            checkout: main
            commit: 6d1fc82bd4581f082d40fef700e62e4c8b45d87e
          untrusted/project_1/github.com/osism/zuul-config:
            canonical_name: github.com/osism/zuul-config
            checkout: main
            commit: 71b711c422c10c242a8f785dc6d41ed9d12e9cd3
          untrusted/project_2/github.com/osism/openinfra-zuul-jobs:
            canonical_name: github.com/osism/openinfra-zuul-jobs
            checkout: master
            commit: 242732586e2cba04d04867d3e302888c766bc231
          untrusted/project_3/github.com/osism/zuul-jobs:
            canonical_name: github.com/osism/zuul-jobs
            checkout: main
            commit: 1d71e58c93442038e695a5d883d57a603182c7ae
        playbooks:
        - path: untrusted/project_0/github.com/osism/python-osism/playbooks/test-integration.yml
          roles:
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_1/zuul-config
            link_target: untrusted/project_1/github.com/osism/zuul-config
            role_path: ansible/playbook_0/role_1/zuul-config/roles
          - checkout: master
            checkout_description: project default branch
            link_name: ansible/playbook_0/role_2/openinfra-zuul-jobs
            link_target: untrusted/project_2/github.com/osism/openinfra-zuul-jobs
            role_path: ansible/playbook_0/role_2/openinfra-zuul-jobs/roles
          - checkout: main
            checkout_description: zuul branch
            link_name: ansible/playbook_0/role_3/zuul-jobs
            link_target: untrusted/project_3/github.com/osism/zuul-jobs
            role_path: ansible/playbook_0/role_3/zuul-jobs/roles
      post_review: false
      post_timeout: 1800
      pre_timeout: null
      project:
        canonical_hostname: github.com
        canonical_name: github.com/osism/python-osism
        name: osism/python-osism
        short_name: python-osism
        src_dir: src/github.com/osism/python-osism
      projects:
        github.com/osism/python-osism:
          canonical_hostname: github.com
          canonical_name: github.com/osism/python-osism
          checkout: main
          checkout_description: zuul branch
          commit: 6d1fc82bd4581f082d40fef700e62e4c8b45d87e
          name: osism/python-osism
          required: false
          short_name: python-osism
          src_dir: src/github.com/osism/python-osism
      ref: refs/pull/2460/head
      resources: {}
      tenant: osism
      timeout: 1800
      topic: null
      voting: true
