This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit 5aca7607cbdf5df72edb71b04d331ca41cdffd75 Author: Andrzej BiaĆecki <[email protected]> AuthorDate: Tue Sep 8 18:53:24 2026 +0200 SOLR-18411: CrossDC: Configure security properties in KafkaMirroringSink initConsumer. (#4883) (cherry picked from commit c43a9a56d199f9405bb0404822ddfd7c1391a936) --- changelog/unreleased/solr-18411.yml | 8 ++ .../solr/crossdc/common/KafkaMirroringSink.java | 3 + .../crossdc/common/KafkaMirroringSinkTest.java | 101 +++++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/changelog/unreleased/solr-18411.yml b/changelog/unreleased/solr-18411.yml new file mode 100644 index 00000000000..9c0db562f88 --- /dev/null +++ b/changelog/unreleased/solr-18411.yml @@ -0,0 +1,8 @@ +title: CrossDC KafkaProducerSink's KafkaConsumer gets no security config + +type: fixed +authors: + - name: Andrzej Bialecki +links: + - name: SOLR-18411 + url: https://issues.apache.org/jira/browse/SOLR-18411 diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/KafkaMirroringSink.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/KafkaMirroringSink.java index 8935160937f..bd7492d1947 100644 --- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/KafkaMirroringSink.java +++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/KafkaMirroringSink.java @@ -214,6 +214,9 @@ public class KafkaMirroringSink implements RequestMirroringSink, Closeable { conf.getInt(KafkaCrossDcConf.MAX_PARTITION_FETCH_BYTES)); kafkaConsumerProperties.put( ConsumerConfig.REQUEST_TIMEOUT_MS_CONFIG, conf.getInt(KafkaCrossDcConf.REQUEST_TIMEOUT_MS)); + + KafkaCrossDcConf.addSecurityProps(conf, kafkaConsumerProperties); + kafkaConsumerProperties.putAll(conf.getAdditionalProperties()); return new KafkaConsumer<>( diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/KafkaMirroringSinkTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/KafkaMirroringSinkTest.java new file mode 100644 index 00000000000..bafded545cc --- /dev/null +++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/KafkaMirroringSinkTest.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.solr.crossdc.common; + +import static org.apache.solr.SolrTestCaseJ4.assumeWorkingMockito; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.kafka.clients.CommonClientConfigs; +import org.apache.kafka.clients.consumer.ConsumerConfig; +import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.KafkaProducer; +import org.apache.kafka.common.config.SslConfigs; +import org.junit.BeforeClass; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; + +public class KafkaMirroringSinkTest { + + @BeforeClass + public static void ensureWorkingMockito() { + assumeWorkingMockito(); + } + + @Test + @SuppressWarnings({"rawtypes", "try"}) + public void testSecurityPropsApplied() throws Exception { + Map<String, Object> properties = new HashMap<>(); + properties.put(KafkaCrossDcConf.TOPIC_NAME, "test-topic"); + properties.put(KafkaCrossDcConf.BOOTSTRAP_SERVERS, "localhost:9092"); + properties.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, "SSL"); + properties.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, "/tmp/truststore.jks"); + properties.put(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG, "changeit"); + + KafkaCrossDcConf conf = new KafkaCrossDcConf(properties); + + // KafkaProducer/KafkaConsumer construction is mocked out so no real client or network is used + // need to suppress "try" warning to ignore args that are unused on the surface + try (MockedConstruction<KafkaProducer> ignoredProducer = mockConstruction(KafkaProducer.class); + MockedConstruction<KafkaConsumer> mockedConsumer = + mockConstruction( + KafkaConsumer.class, + // need this topic, too + (mock, context) -> + when(mock.listTopics()).thenReturn(Map.of("test-topic", List.of()))); + MockedStatic<KafkaCrossDcConf> mockedStatic = + mockStatic(KafkaCrossDcConf.class, CALLS_REAL_METHODS)) { + + // collect all newly created properties + ArgumentCaptor<Properties> propsCaptor = ArgumentCaptor.forClass(Properties.class); + + // create the sink, which will create the producer and consumer + // suppress "try" warning because of unused sink + try (KafkaMirroringSink sink = new KafkaMirroringSink(conf)) { + mockedStatic.verify( + () -> KafkaCrossDcConf.addSecurityProps(same(conf), propsCaptor.capture()), times(2)); + } + + List<Properties> allProps = propsCaptor.getAllValues(); + Properties producerProps = allProps.get(0); + Properties consumerProps = allProps.get(1); + + // check that the captured producer props and the consumer props are filled in + assertTrue(producerProps.containsKey("key.serializer")); + assertTrue(consumerProps.containsKey(ConsumerConfig.GROUP_ID_CONFIG)); + + for (Properties props : allProps) { + assertEquals("SSL", props.getProperty(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG)); + assertEquals( + "/tmp/truststore.jks", props.getProperty(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG)); + assertEquals("changeit", props.getProperty(SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG)); + } + } + } +}
