Skip to content

Commit 66d1861

Browse files
authored
Merge pull request #151 from stfc/Connect_to_rabbit_hosts_directly
ENH: Add support for multiple rabbit hosts in con string
2 parents 62c065e + 94117d4 commit 66d1861

File tree

7 files changed

+98
-29
lines changed

7 files changed

+98
-29
lines changed

OpenStack-Rabbit-Consumer/rabbit_consumer/consumer_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class _RabbitFields:
4949
environment variables.
5050
"""
5151

52-
rabbit_host: str = field(default_factory=partial(os.getenv, "RABBIT_HOST", None))
52+
rabbit_hosts: str = field(default_factory=partial(os.getenv, "RABBIT_HOST", None))
5353
rabbit_port: str = field(default_factory=partial(os.getenv, "RABBIT_PORT", None))
5454
rabbit_username: str = field(
5555
default_factory=partial(os.getenv, "RABBIT_USERNAME", None)

OpenStack-Rabbit-Consumer/rabbit_consumer/message_consumer.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,33 @@ def on_message(message: rabbitpy.Message) -> None:
254254
message.ack()
255255

256256

257+
def generate_login_str(config: ConsumerConfig) -> str:
258+
"""
259+
Generates the login string for the rabbit connection.
260+
"""
261+
if not config.rabbit_hosts:
262+
raise ValueError("No rabbit hosts provided")
263+
264+
if not isinstance(config.rabbit_hosts, str):
265+
raise ValueError("Rabbit hosts must be a comma separated string of hosts")
266+
267+
debug_str = "amqp://"
268+
connect_str = "amqp://"
269+
270+
for host in config.rabbit_hosts.split(","):
271+
host = host.strip()
272+
connect_str += f"{config.rabbit_username}:{config.rabbit_password}@{host}:{config.rabbit_port},"
273+
debug_str += f"{config.rabbit_username}:<password>@{host}:{config.rabbit_port},"
274+
275+
# Trim the trailing comma
276+
connect_str = connect_str[:-1]
277+
debug_str = debug_str[:-1]
278+
279+
logger.debug("Connecting to rabbit with: %s", debug_str)
280+
281+
return connect_str
282+
283+
257284
def initiate_consumer() -> None:
258285
"""
259286
Initiates the message consumer and starts consuming messages in a loop.
@@ -263,18 +290,10 @@ def initiate_consumer() -> None:
263290
# Ensure we have valid creds before trying to contact rabbit
264291
verify_kerberos_ticket()
265292

266-
config = ConsumerConfig()
267-
268-
host = config.rabbit_host
269-
port = config.rabbit_port
270-
login_user = config.rabbit_username
271-
login_pass = config.rabbit_password
272-
logger.debug(
273-
"Connecting to rabbit with: amqp://%s:<password>@%s:%s/", login_user, host, port
274-
)
275293
exchanges = ["nova"]
276294

277-
login_str = f"amqp://{login_user}:{login_pass}@{host}:{port}/"
295+
config = ConsumerConfig()
296+
login_str = generate_login_str(config)
278297
with rabbitpy.Connection(login_str) as conn:
279298
with conn.channel() as channel:
280299
logger.debug("Connected to RabbitMQ")

OpenStack-Rabbit-Consumer/tests/test_consumer_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
]
2222

2323
RABBIT_FIELDS = [
24-
("rabbit_host", "RABBIT_HOST"),
24+
("rabbit_hosts", "RABBIT_HOST"),
2525
("rabbit_port", "RABBIT_PORT"),
2626
("rabbit_username", "RABBIT_USERNAME"),
2727
("rabbit_password", "RABBIT_PASSWORD"),

OpenStack-Rabbit-Consumer/tests/test_message_consumer.py

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
is_aq_managed_image,
2222
get_aq_build_metadata,
2323
delete_machine,
24+
generate_login_str,
2425
)
2526
from rabbit_consumer.vm_data import VmData
2627

@@ -101,34 +102,82 @@ def test_on_message_accepts_event_types(message_event_type, consume, event_type)
101102
message.ack.assert_called_once()
102103

103104

104-
# pylint: disable=too-few-public-methods
105-
class MockedConfig(ConsumerConfig):
105+
@pytest.fixture(name="mocked_config")
106+
def mocked_config_fixture() -> ConsumerConfig:
106107
"""
107108
Provides a mocked input config for the consumer
108109
"""
110+
config = ConsumerConfig()
109111

110-
rabbit_host = "rabbit_host"
111-
rabbit_port = 1234
112-
rabbit_username = "rabbit_username"
113-
rabbit_password = "rabbit_password"
112+
# Note: the mismatched spaces are intentional
113+
config.rabbit_hosts = "rabbit_host1, rabbit_host2,rabbit_host3"
114+
config.rabbit_port = 1234
115+
config.rabbit_username = "rabbit_username"
116+
config.rabbit_password = "rabbit_password"
117+
return config
118+
119+
120+
def test_generate_login_str(mocked_config):
121+
"""
122+
Test that the function generates the correct login string
123+
"""
124+
expected = (
125+
"amqp://"
126+
"rabbit_username:rabbit_password@rabbit_host1:1234,"
127+
"rabbit_username:rabbit_password@rabbit_host2:1234,"
128+
"rabbit_username:rabbit_password@rabbit_host3:1234"
129+
)
130+
131+
assert generate_login_str(mocked_config) == expected
132+
133+
134+
def test_generate_login_str_no_hosts(mocked_config):
135+
"""
136+
Test that the function raises if nothing is passed
137+
"""
138+
mocked_config.rabbit_hosts = ""
139+
with pytest.raises(ValueError):
140+
assert generate_login_str(mocked_config)
141+
142+
143+
def test_generate_login_non_str(mocked_config):
144+
"""
145+
Test that the function raises if the input is not a string
146+
"""
147+
mocked_config.rabbit_hosts = 1234
148+
with pytest.raises(ValueError):
149+
assert generate_login_str(mocked_config)
150+
151+
152+
@patch("rabbit_consumer.message_consumer.logger")
153+
def test_password_does_not_get_logged(logging, mocked_config):
154+
"""
155+
Test that the password does not get logged
156+
"""
157+
returned_str = generate_login_str(mocked_config)
158+
logging.debug.assert_called_once()
159+
logging_arg = logging.debug.call_args[0][1]
160+
assert mocked_config.rabbit_password in returned_str
161+
162+
# Check that the password is not in the log message
163+
assert mocked_config.rabbit_username in logging_arg
164+
assert mocked_config.rabbit_password not in logging_arg
114165

115166

116167
@patch("rabbit_consumer.message_consumer.verify_kerberos_ticket")
168+
@patch("rabbit_consumer.message_consumer.generate_login_str")
117169
@patch("rabbit_consumer.message_consumer.rabbitpy")
118-
def test_initiate_consumer_channel_setup(rabbitpy, _):
170+
def test_initiate_consumer_channel_setup(rabbitpy, gen_login, _, mocked_config):
119171
"""
120172
Test that the function sets up the channel and queue correctly
121173
"""
122-
mocked_config = MockedConfig()
123-
124174
with patch("rabbit_consumer.message_consumer.ConsumerConfig") as config:
125175
config.return_value = mocked_config
126176
initiate_consumer()
127177

128-
rabbitpy.Connection.assert_called_once_with(
129-
f"amqp://{mocked_config.rabbit_username}:{mocked_config.rabbit_password}@{mocked_config.rabbit_host}:{mocked_config.rabbit_port}/"
130-
)
178+
gen_login.assert_called_once_with(mocked_config)
131179

180+
rabbitpy.Connection.assert_called_once_with(gen_login.return_value)
132181
connection = rabbitpy.Connection.return_value.__enter__.return_value
133182
connection.channel.assert_called_once()
134183
channel = connection.channel.return_value.__enter__.return_value
@@ -149,7 +198,8 @@ def test_initiate_consumer_actual_consumption(rabbitpy, message_mock, _):
149198
# We need our mocked queue to act like a generator
150199
rabbitpy.Queue.return_value.__iter__.return_value = queue_messages
151200

152-
initiate_consumer()
201+
with patch("rabbit_consumer.message_consumer.generate_login_str"):
202+
initiate_consumer()
153203

154204
message_mock.assert_has_calls([call(message) for message in queue_messages])
155205

OpenStack-Rabbit-Consumer/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.3.7
1+
3.0.0

charts/rabbit-consumer/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ type: application
66
# This is the chart version. This version number should be incremented each time you make changes
77
# to the chart and its templates, including the app version.
88
# Versions are expected to follow Semantic Versioning (https://semver.org/)
9-
version: 1.6.1
9+
version: 1.7.0
1010

1111
# This is the version number of the application being deployed. This version number should be
1212
# incremented each time you make changes to the application. Versions are not expected to
1313
# follow Semantic Versioning. They should reflect the version the application is using.
1414
# It is recommended to use it with quotes.
15-
appVersion: "v2.3.7"
15+
appVersion: "v3.0.0"

charts/rabbit-consumer/prod-values.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ consumer:
33
defaultPrefix: vm-openstack-Prod-
44

55
rabbitmq:
6-
host: openstack.stfc.ac.uk
6+
host: hv747.nubes.rl.ac.uk
77

88
openstack:
99
authUrl: https://openstack.stfc.ac.uk:5000/v3

0 commit comments

Comments
 (0)