Skip to content

Commit 71f7280

Browse files
committed
Message broker: add test script
This is needed for the composition tests, but we feel it is a useful tool to have in your belt. Change-Id: I4bf6e7ed1cc6b9a3145c0aba09189cf7d89db791
1 parent df8fc8e commit 71f7280

1 file changed

Lines changed: 145 additions & 0 deletions

File tree

bin/cmk-broker-test

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
#!/usr/bin/env python3
2+
# Copyright (C) 2024 Checkmk GmbH - License: GNU General Public License v2
3+
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
4+
# conditions defined in the file COPYING, which is part of this source code package.
5+
"""cmk-broker-test
6+
7+
A debugging tool for Checkmks builtin message broker. This tests if sites of a shared distributed setup can communicate via the message broker.
8+
Some debugging information is printed to the console, but you cannot expect this to be a stable interface.
9+
10+
You can start this application in two modes:
11+
12+
When the SITE argument is given, the application will send a message to the given site and wait for a response.
13+
It will exit successfully if the response contains the expected UUID, otherwise it will exit with some error code.
14+
15+
When the SITE argument is omitted, the application will start listening for incoming messages.
16+
It will respond to each message with a message of its own, containing the same UUID.
17+
"""
18+
19+
import argparse
20+
import sys
21+
import time
22+
from collections.abc import Callable
23+
from dataclasses import dataclass
24+
from typing import Self
25+
from uuid import UUID, uuid4
26+
27+
from pydantic import BaseModel
28+
29+
from cmk.ccc.site import omd_site
30+
31+
from cmk.utils.paths import omd_root
32+
33+
from cmk.messaging import AppName, Channel, Connection, DeliveryTag, QueueName, RoutingKey
34+
35+
36+
class TestMessage(BaseModel):
37+
publisher_site: str
38+
uuid: UUID
39+
timestamp: float
40+
41+
@classmethod
42+
def new(cls, uuid: UUID | None = None) -> Self:
43+
return cls(publisher_site=omd_site(), uuid=uuid or uuid4(), timestamp=time.time())
44+
45+
46+
APP_NAME = AppName(__doc__.split("\n", 1)[0])
47+
48+
QUEUE_NAME = QueueName("debugging-test")
49+
50+
ROUTING_KEY = RoutingKey(QUEUE_NAME.value)
51+
52+
53+
@dataclass(frozen=True)
54+
class Arguments:
55+
site: str | None
56+
57+
58+
def parse_arguments(args: list[str]) -> Arguments:
59+
prog, descr = __doc__.split("\n", 1)
60+
parser = argparse.ArgumentParser(
61+
prog=prog, description=descr, formatter_class=argparse.RawDescriptionHelpFormatter
62+
)
63+
parser.add_argument("site", nargs="?", help="The site to send the message to")
64+
return Arguments(site=None if (site := parser.parse_args(args).site) is None else str(site))
65+
66+
67+
def _callback_pong(
68+
channel: Channel[TestMessage], delivery_tag: DeliveryTag, received: TestMessage
69+
) -> None:
70+
"""Upon receiving a message, publish a response"""
71+
response = TestMessage.new(received.uuid)
72+
sys.stdout.write(
73+
"===================================\n"
74+
"Received message:\n"
75+
f" {received!r}\n"
76+
f"Received after {(response.timestamp - received.timestamp)*1000:.3f} ms\n"
77+
"Responding with message:\n"
78+
f" {response!r}\n"
79+
)
80+
channel.publish_for_site(received.publisher_site, response, routing=ROUTING_KEY)
81+
channel.acknowledge(delivery_tag)
82+
83+
84+
def _command_pong() -> int:
85+
sys.stdout.write("Establishing connection to local broker\n")
86+
with Connection(APP_NAME, omd_root) as conn:
87+
channel = conn.channel(TestMessage)
88+
channel.queue_declare(queue=QUEUE_NAME)
89+
sys.stdout.write("Waiting for messages\n")
90+
channel.consume(QUEUE_NAME, _callback_pong)
91+
return 42 # can't happen
92+
93+
94+
def _make_callback_ping(
95+
sent: TestMessage,
96+
) -> Callable[[Channel[TestMessage], DeliveryTag, TestMessage], None]:
97+
def _callback_ping(
98+
channel: Channel[TestMessage], delivery_tag: DeliveryTag, received: TestMessage
99+
) -> None:
100+
now = time.time()
101+
sys.stdout.write(
102+
"Received message:\n"
103+
f" {received!r}\n"
104+
f"Received after {(now - received.timestamp)*1000:.3f} ms\n"
105+
f"Roundtrip: {(now - sent.timestamp)*1000:.3f} ms\n"
106+
)
107+
channel.acknowledge(delivery_tag)
108+
109+
if not received.uuid == sent.uuid:
110+
sys.stdout.write(
111+
"The received message was not sent in response to my message (the UUIDs don't match).\n"
112+
)
113+
sys.exit(1)
114+
sys.stdout.write("UUIDs match\n")
115+
sys.exit(0)
116+
117+
return _callback_ping
118+
119+
120+
def _command_ping(site_id: str) -> int:
121+
sys.stdout.write("Establishing connection to local broker\n")
122+
with Connection(APP_NAME, omd_root) as conn:
123+
channel = conn.channel(TestMessage)
124+
message = TestMessage.new()
125+
sys.stdout.write("Sending message:\n" f" {message!r}\n")
126+
channel.publish_for_site(site_id, message, routing=ROUTING_KEY)
127+
128+
channel.queue_declare(queue=QUEUE_NAME)
129+
sys.stdout.write("Waiting for response\n")
130+
channel.consume(QUEUE_NAME, _make_callback_ping(message))
131+
132+
return 23 # can't happen
133+
134+
135+
def main() -> int:
136+
args = parse_arguments(sys.argv[1:])
137+
try:
138+
return _command_pong() if args.site is None else _command_ping(args.site)
139+
except KeyboardInterrupt:
140+
sys.stdout.write("\nExiting\n")
141+
return 0
142+
143+
144+
if __name__ == "__main__":
145+
sys.exit(main())

0 commit comments

Comments
 (0)