Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions faust/transport/drivers/aiokafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -1702,6 +1702,15 @@ def credentials_to_aiokafka_auth(
"ssl_context": credentials.ssl_context,
}
elif isinstance(credentials, SASLCredentials):
if credentials.mechanism.value == "PLAIN" and (
credentials.username is None or credentials.password is None
):
raise ImproperlyConfigured(
"SASL/PLAIN requires both username and password in "
"broker_credentials=SASLCredentials(...). "
"Check the credentials configured on the App that owns "
"the topic before creating its producer or consumer."
)
return {
"security_protocol": credentials.protocol.value,
"sasl_mechanism": credentials.mechanism.value,
Expand Down
51 changes: 51 additions & 0 deletions tests/unit/transport/drivers/test_aiokafka.py
Original file line number Diff line number Diff line change
Expand Up @@ -1580,6 +1580,43 @@ async def test__new_producer__default(self, *, app):
p = producer._new_producer()
assert isinstance(p, aiokafka.AIOKafkaProducer)

@pytest.mark.asyncio
@pytest.mark.conf(
processing_guarantee="exactly_once",
broker="kafka://localhost:9098",
broker_credentials=auth.SASLCredentials(
username="uname",
password=("p" "w"),
mechanism="PLAIN",
),
)
async def test_on_start__passes_sasl_plain_credentials(self, *, app):
producer = Producer(app.transport)
assert app.in_transaction is False
with patch("aiokafka.AIOKafkaProducer") as AIOKafkaProducer:
AIOKafkaProducer.return_value.start = AsyncMock()
await producer.on_start()
AIOKafkaProducer.assert_called_once_with(
bootstrap_servers=["localhost:9098"],
client_id=f"faust-{faust.__version__}",
acks=-1,
linger_ms=0,
max_batch_size=16384,
max_request_size=1000000,
compression_type=None,
security_protocol="SASL_PLAINTEXT",
partitioner=producer.partitioner,
request_timeout_ms=1200000,
metadata_max_age_ms=300000,
connections_max_idle_ms=540000,
sasl_mechanism="PLAIN",
sasl_plain_username="uname",
sasl_plain_password="pw",
ssl_context=None,
transactional_id=None,
**({"api_version": "auto"} if _AIOKAFKA_HAS_API_VERSION else {}),
)

def test__producer_type(self, *, producer, app):
assert producer._producer_type is aiokafka.AIOKafkaProducer

Expand Down Expand Up @@ -2122,3 +2159,17 @@ def test_ensure_aiokafka_TPset():
actual = ensure_aiokafka_TPset({TP(topic="foo", partition=0)})
assert actual == {TopicPartition("foo", 0)}
assert all(isinstance(tp, TopicPartition) for tp in actual)


@pytest.mark.parametrize(
"username,password",
[(None, None), (None, "bar"), ("foo", None)],
)
def test_credentials_to_aiokafka__sasl_plain_missing_credentials(username, password):
credentials = auth.SASLCredentials(
username=username, password=password, mechanism="PLAIN"
)
with pytest.raises(ImproperlyConfigured, match="broker_credentials") as exc:
credentials_to_aiokafka_auth(credentials)
assert "foo" not in str(exc.value)
assert "bar" not in str(exc.value)
Loading