This is an automated email from the ASF dual-hosted git repository.

sarathsubramanian pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/atlas.git


The following commit(s) were added to refs/heads/master by this push:
     new 4bec89039 ATLAS-5336: Upgrade Kafka to 3.9.1; embedded notification 
broker uses KRaft (no ZooKeeper) (#689)
4bec89039 is described below

commit 4bec890393d06abea98ee135472b4df9f25d0d87
Author: Ramachandran Krishnan <[email protected]>
AuthorDate: Wed Jul 8 09:55:12 2026 +0530

    ATLAS-5336: Upgrade Kafka to 3.9.1; embedded notification broker uses KRaft 
(no ZooKeeper) (#689)
    
    * ATLAS-5336: Upgrade Kafka to 3.9.1; embedded notification broker uses 
KRaft
    
    Bump kafka-clients and embedded test broker from 2.8.2 to 3.9.1 (Scala 
2.13),
    replace ZooKeeper-based EmbeddedKafkaServer with KafkaClusterTestKit, and
    update docker dev Kafka image packaging to kafka_2.13-3.9.1.
    
    * Document jackson-jaxrs 2.12.7 pin for Jersey 1.19 compatibility.
    
    Point readers to ATLAS-5190 for full Jackson unification via Jersey 2.x.
    
    * Remove unused junit-jupiter-api from atlas-notification compile deps.
    
    It leaked to falcon-bridge via atlas-notification and caused Failsafe to
    select JUnit Platform, which then failed on transitive junit 3.8.1 from
    falcon-common during FalconHookIT discovery.
    
    * Scope Kafka test and unit test deps in notification to test.
    
    Kafka 3.9 TestUtils needs junit-jupiter-api at test runtime; keep it off the
    compile classpath so falcon-bridge Failsafe stays on TestNG. Align mockito 
and
    testng with other Atlas modules (test scope).
    
    * Add test-scoped junit-jupiter-api to webapp for Kafka embedded broker 
tests.
    
    NotificationHookConsumerKafkaTest starts EmbeddedKafkaServer, which uses 
Kafka
    3.9 TestUtils requiring JUnit Jupiter on the test classpath. Test-scoped 
deps
    from atlas-notification do not propagate to webapp.
    
    * Fix flaky ImportTaskListenerImplTest async consumer assertions on CI.
    
    Shut down the import executor in @AfterMethod and wait with CountDownLatch
    instead of Thread.sleep so Mockito state does not leak between tests.
    
    * Harden BasicSearchIT and AdminExportImportTestIT against CI flakes.
    
    Scope hive searches to largedb. entities from the imported test dataset
    instead of the shared @cl1 cluster suffix, and tolerate Stream Closed
    during AdminExportImportTestIT teardown when the embedded server stops.
    
    * Fix IT scoping and Failsafe/TestNG classpath for Kafka 3.9.
    
    Scope BasicSearchIT to default. entities from hive-db-50-tables.zip instead
    of the shared @cl1 suffix, skip scoping for fulltext and paginated searches,
    keep junit-jupiter on the notification compile classpath for embedded KRaft
    runtime, and pin Failsafe to TestNG after junit landed on the webapp test 
path.
    
    * Bind embedded KRaft broker to configured bootstrap.servers for ITs.
    
    Derive EXTERNAL and CONTROLLER listeners from atlas.kafka.bootstrap.servers 
so Jetty and Failsafe JVMs share the same broker port, fixing 
NotificationHookConsumerIT topic timeouts on CI.
    
    * Pre-serialize classification request bodies to avoid Codehaus Jackson 
conflicts.
    
    HiveHookIT fails when Jersey serializes AtlasClassification because 
isPropagate()
    and getPropagate() both map to "propagate"; AtlasType.toJson() matches the 
entity fix.
    
    * Pin Jackson 2.16 on sqoop-bridge IT classpath for AtlasClient v1.
    
    SqoopHookIT fails with NoClassDefFoundError for StreamConstraintsException
    when jackson-databind 2.16 is paired with an older jackson-core from 
Hadoop/Hive.
    
    * Pin Jackson 2.16 databind on storm-bridge IT classpath for AtlasClient v1.
    
    StormAtlasHookIT fails with NoSuchFieldError for 
READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE
    when jackson-databind 2.16 is mixed with an older databind from storm-core 
on the test classpath.
    
    Co-authored-by: Cursor <[email protected]>
    
    * Skip trino-extractor Failsafe ITs on default mvn verify.
    
    Pinning Failsafe to TestNG for the Kafka upgrade requires testng on the
    module test classpath; the extractor only has a stub IT and no test deps 
yet.
    
    ---------
    
    Co-authored-by: ramk <[email protected]>
    Co-authored-by: Cursor <[email protected]>
---
 addons/sqoop-bridge/pom.xml                        |  13 ++
 addons/storm-bridge/pom.xml                        |   8 +-
 addons/trino-extractor/pom.xml                     |   2 +
 .../main/java/org/apache/atlas/AtlasClientV2.java  |   8 +-
 dev-support/atlas-docker/.env                      |   2 +-
 dev-support/atlas-docker/Dockerfile.atlas-kafka    |   9 +-
 dev-support/atlas-docker/download-archives.sh      |   2 +-
 distro/src/conf/atlas-application.properties       |   5 +-
 notification/pom.xml                               |  57 +++++-
 .../apache/atlas/kafka/EmbeddedKafkaServer.java    | 212 +++++++++++++--------
 .../org/apache/atlas/kafka/KafkaNotification.java  |  11 ++
 pom.xml                                            |  15 +-
 webapp/pom.xml                                     |  33 +++-
 .../notification/ImportTaskListenerImplTest.java   |  52 +++--
 .../atlas/web/integration/BasicSearchIT.java       |   6 +-
 .../web/resources/AdminExportImportTestIT.java     |  22 +++
 16 files changed, 331 insertions(+), 126 deletions(-)

diff --git a/addons/sqoop-bridge/pom.xml b/addons/sqoop-bridge/pom.xml
index ea1bc94ba..f4954624c 100644
--- a/addons/sqoop-bridge/pom.xml
+++ b/addons/sqoop-bridge/pom.xml
@@ -34,6 +34,12 @@
 
     <dependencies>
 
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.databind.version}</version>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.atlas</groupId>
             <artifactId>atlas-client-v1</artifactId>
@@ -129,6 +135,13 @@
             </exclusions>
         </dependency>
 
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+            <version>${jackson.version}</version>
+            <scope>test</scope>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.atlas</groupId>
             <artifactId>atlas-graphdb-impls</artifactId>
diff --git a/addons/storm-bridge/pom.xml b/addons/storm-bridge/pom.xml
index 42e325f1c..1033a3739 100644
--- a/addons/storm-bridge/pom.xml
+++ b/addons/storm-bridge/pom.xml
@@ -33,6 +33,12 @@
     <description>Apache Atlas Storm Bridge Module</description>
 
     <dependencies>
+        <!-- apache atlas core dependencies -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <version>${jackson.databind.version}</version>
+        </dependency>
 
         <dependency>
             <groupId>commons-collections</groupId>
@@ -55,7 +61,7 @@
             <artifactId>metrics-graphite</artifactId>
             <version>${dropwizard-metrics}</version>
         </dependency>
-        <!-- apache atlas core dependencies -->
+
         <dependency>
             <groupId>org.apache.atlas</groupId>
             <artifactId>atlas-client-v1</artifactId>
diff --git a/addons/trino-extractor/pom.xml b/addons/trino-extractor/pom.xml
index b66b957ca..9a15f3edf 100644
--- a/addons/trino-extractor/pom.xml
+++ b/addons/trino-extractor/pom.xml
@@ -35,6 +35,8 @@
     <properties>
         <checkstyle.failOnViolation>true</checkstyle.failOnViolation>
         <checkstyle.skip>false</checkstyle.skip>
+        <!-- No live-stack ITs yet; avoid Failsafe/TestNG on default mvn 
verify (see ATLAS-5338). -->
+        <skipITs>true</skipITs>
     </properties>
 
     <dependencies>
diff --git a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java 
b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java
index daa600949..c7260ff6c 100644
--- a/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java
+++ b/client/client-v2/src/main/java/org/apache/atlas/AtlasClientV2.java
@@ -518,23 +518,23 @@ public class AtlasClientV2 extends AtlasBaseClient {
     }
 
     public void addClassifications(String guid, List<AtlasClassification> 
classifications) throws AtlasServiceException {
-        callAPI(formatPathParameters(API_V2.ADD_CLASSIFICATIONS, guid), 
(Class<?>) null, classifications, (String[]) null);
+        callAPI(formatPathParameters(API_V2.ADD_CLASSIFICATIONS, guid), 
(Class<?>) null, AtlasType.toJson(classifications), (String[]) null);
     }
 
     public void addClassifications(String typeName, Map<String, String> 
uniqAttributes, List<AtlasClassification> classifications) throws 
AtlasServiceException {
         MultivaluedMap<String, String> queryParams = 
attributesToQueryParams(uniqAttributes);
 
-        
callAPI(formatPathParameters(API_V2.ADD_CLASSIFICATION_BY_TYPE_AND_ATTRIBUTE, 
typeName), (Class<?>) null, classifications, queryParams);
+        
callAPI(formatPathParameters(API_V2.ADD_CLASSIFICATION_BY_TYPE_AND_ATTRIBUTE, 
typeName), (Class<?>) null, AtlasType.toJson(classifications), queryParams);
     }
 
     public void updateClassifications(String guid, List<AtlasClassification> 
classifications) throws AtlasServiceException {
-        callAPI(formatPathParameters(API_V2.UPDATE_CLASSIFICATIONS, guid), 
(Class<?>) null, classifications);
+        callAPI(formatPathParameters(API_V2.UPDATE_CLASSIFICATIONS, guid), 
(Class<?>) null, AtlasType.toJson(classifications));
     }
 
     public void updateClassifications(String typeName, Map<String, String> 
uniqAttributes, List<AtlasClassification> classifications) throws 
AtlasServiceException {
         MultivaluedMap<String, String> queryParams = 
attributesToQueryParams(uniqAttributes);
 
-        
callAPI(formatPathParameters(API_V2.UPDATE_CLASSIFICATION_BY_TYPE_AND_ATTRIBUTE,
 typeName), (Class<?>) null, classifications, queryParams);
+        
callAPI(formatPathParameters(API_V2.UPDATE_CLASSIFICATION_BY_TYPE_AND_ATTRIBUTE,
 typeName), (Class<?>) null, AtlasType.toJson(classifications), queryParams);
     }
 
     public String setClassifications(AtlasEntityHeaders entityHeaders) throws 
AtlasServiceException {
diff --git a/dev-support/atlas-docker/.env b/dev-support/atlas-docker/.env
index c07b9fb63..2041f68aa 100644
--- a/dev-support/atlas-docker/.env
+++ b/dev-support/atlas-docker/.env
@@ -21,7 +21,7 @@ ATLAS_VERSION=3.0.0-SNAPSHOT
 UBUNTU_VERSION=20.04
 HADOOP_VERSION=3.4.2
 HBASE_VERSION=2.6.4
-KAFKA_VERSION=2.8.2
+KAFKA_VERSION=3.9.1
 HIVE_VERSION=4.1.0
 
 ATLAS_BACKEND=hbase
diff --git a/dev-support/atlas-docker/Dockerfile.atlas-kafka 
b/dev-support/atlas-docker/Dockerfile.atlas-kafka
index 5c5dba802..4406fbd90 100644
--- a/dev-support/atlas-docker/Dockerfile.atlas-kafka
+++ b/dev-support/atlas-docker/Dockerfile.atlas-kafka
@@ -18,17 +18,18 @@ FROM atlas-base:latest
 
 ARG ATLAS_VERSION
 ARG KAFKA_VERSION
+ARG KAFKA_SCALA_VERSION=2.13
 
 COPY ./dist/apache-atlas-${ATLAS_VERSION}-kafka-hook.tar.gz /home/atlas/dist/
-COPY ./downloads/kafka_2.12-${KAFKA_VERSION}.tgz            /home/atlas/dist/
+COPY ./downloads/kafka_${KAFKA_SCALA_VERSION}-${KAFKA_VERSION}.tgz            
/home/atlas/dist/
 
 COPY ./scripts/atlas-kafka-setup.sh               /home/atlas/scripts/
 COPY ./scripts/atlas-kafka.sh                     /home/atlas/scripts/
 COPY ./scripts/atlas-kafka-application.properties /home/atlas/scripts/
 
-RUN tar xvfz /home/atlas/dist/kafka_2.12-${KAFKA_VERSION}.tgz 
--directory=/opt/ && \
-    ln -s /opt/kafka_2.12-${KAFKA_VERSION} /opt/kafka && \
-    rm -f /home/atlas/dist/kafka_2.12-${KAFKA_VERSION}.tgz && \
+RUN tar xvfz 
/home/atlas/dist/kafka_${KAFKA_SCALA_VERSION}-${KAFKA_VERSION}.tgz 
--directory=/opt/ && \
+    ln -s /opt/kafka_${KAFKA_SCALA_VERSION}-${KAFKA_VERSION} /opt/kafka && \
+    rm -f /home/atlas/dist/kafka_${KAFKA_SCALA_VERSION}-${KAFKA_VERSION}.tgz 
&& \
     tar xvfz /home/atlas/dist/apache-atlas-${ATLAS_VERSION}-kafka-hook.tar.gz 
--directory=/opt/ && \
     ln -s /opt/apache-atlas-kafka-hook-${ATLAS_VERSION} 
/opt/apache-atlas-kafka-hook && \
     rm -f /home/atlas/dist/apache-atlas-${ATLAS_VERSION}-kafka-hook.tar.gz && \
diff --git a/dev-support/atlas-docker/download-archives.sh 
b/dev-support/atlas-docker/download-archives.sh
index 5c22fe73b..17ea80aff 100755
--- a/dev-support/atlas-docker/download-archives.sh
+++ b/dev-support/atlas-docker/download-archives.sh
@@ -46,6 +46,6 @@ downloadIfNotPresent() {
 
 downloadIfNotPresent hadoop-${HADOOP_VERSION}.tar.gz        
https://archive.apache.org/dist/hadoop/common/hadoop-${HADOOP_VERSION}
 downloadIfNotPresent hbase-${HBASE_VERSION}-bin.tar.gz      
https://archive.apache.org/dist/hbase/${HBASE_VERSION}
-downloadIfNotPresent kafka_2.12-${KAFKA_VERSION}.tgz        
https://archive.apache.org/dist/kafka/${KAFKA_VERSION}
+downloadIfNotPresent kafka_2.13-${KAFKA_VERSION}.tgz        
https://archive.apache.org/dist/kafka/${KAFKA_VERSION}
 downloadIfNotPresent apache-hive-${HIVE_VERSION}-bin.tar.gz 
https://archive.apache.org/dist/hive/hive-${HIVE_VERSION}
 
diff --git a/distro/src/conf/atlas-application.properties 
b/distro/src/conf/atlas-application.properties
index 126143a20..35ba5a3db 100755
--- a/distro/src/conf/atlas-application.properties
+++ b/distro/src/conf/atlas-application.properties
@@ -90,11 +90,8 @@ atlas.graph.index.search.max-result-set-size=150
 #########  Notification Configs  #########
 atlas.notification.embedded=true
 atlas.kafka.data=${sys:atlas.home}/data/kafka
-atlas.kafka.zookeeper.connect=localhost:9026
+# Embedded broker uses KRaft (no ZooKeeper). bootstrap.servers is updated at 
runtime when embedded=true.
 atlas.kafka.bootstrap.servers=localhost:9027
-atlas.kafka.zookeeper.session.timeout.ms=400
-atlas.kafka.zookeeper.connection.timeout.ms=200
-atlas.kafka.zookeeper.sync.time.ms=20
 atlas.kafka.auto.commit.interval.ms=1000
 atlas.kafka.hook.group.id=atlas
 
diff --git a/notification/pom.xml b/notification/pom.xml
index 1885f7c8d..22c1f6021 100644
--- a/notification/pom.xml
+++ b/notification/pom.xml
@@ -63,21 +63,49 @@
             <groupId>org.apache.kafka</groupId>
             <artifactId>kafka-clients</artifactId>
         </dependency>
-
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-clients</artifactId>
+            <version>${kafka.version}</version>
+            <classifier>test</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-metadata</artifactId>
+            <version>${kafka.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-server</artifactId>
+            <version>${kafka.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-server-common</artifactId>
+            <version>${kafka.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka-server-common</artifactId>
+            <version>${kafka.version}</version>
+            <classifier>test</classifier>
+        </dependency>
         <dependency>
             <groupId>org.apache.kafka</groupId>
             <artifactId>kafka_${kafka.scala.binary.version}</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-core</artifactId>
-            <version>${mockito.version}</version>
+            <groupId>org.apache.kafka</groupId>
+            <artifactId>kafka_${kafka.scala.binary.version}</artifactId>
+            <version>${kafka.version}</version>
+            <classifier>test</classifier>
         </dependency>
 
+        <!-- KafkaClusterTestKit (embedded KRaft broker) calls 
org.apache.kafka.test.TestUtils, which uses junit.jupiter at runtime in the 
WAR. -->
         <dependency>
-            <groupId>org.testng</groupId>
-            <artifactId>testng</artifactId>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <version>5.11.4</version>
         </dependency>
 
         <dependency>
@@ -94,6 +122,19 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <version>${mockito.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.testng</groupId>
+            <artifactId>testng</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -172,7 +213,7 @@
                                 <artifactItem>
                                     <groupId>org.apache.zookeeper</groupId>
                                     <artifactId>zookeeper</artifactId>
-                                    <version>3.4.6</version>
+                                    <version>${zookeeper.version}</version>
                                 </artifactItem>
                             </artifactItems>
                         </configuration>
diff --git 
a/notification/src/main/java/org/apache/atlas/kafka/EmbeddedKafkaServer.java 
b/notification/src/main/java/org/apache/atlas/kafka/EmbeddedKafkaServer.java
index 23158f521..654caa7d9 100644
--- a/notification/src/main/java/org/apache/atlas/kafka/EmbeddedKafkaServer.java
+++ b/notification/src/main/java/org/apache/atlas/kafka/EmbeddedKafkaServer.java
@@ -17,36 +17,29 @@
  */
 package org.apache.atlas.kafka;
 
-import kafka.server.KafkaConfig;
-import kafka.server.KafkaServer;
-import kafka.zookeeper.ZooKeeperClientException;
+import kafka.testkit.KafkaClusterTestKit;
+import kafka.testkit.TestKitNodes;
 import org.apache.atlas.ApplicationProperties;
 import org.apache.atlas.AtlasException;
 import org.apache.atlas.service.Service;
 import org.apache.atlas.util.CommandHandlerUtility;
 import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ConfigurationConverter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.kafka.clients.CommonClientConfigs;
 import org.apache.kafka.clients.producer.ProducerConfig;
-import org.apache.kafka.common.KafkaException;
-import org.apache.kafka.common.utils.Time;
-import org.apache.zookeeper.server.NIOServerCnxnFactory;
-import org.apache.zookeeper.server.ServerCnxnFactory;
-import org.apache.zookeeper.server.ZooKeeperServer;
+import org.apache.kafka.common.utils.Exit;
+import org.apache.kafka.common.utils.Utils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
-import scala.Option;
 
 import javax.inject.Inject;
 
 import java.io.File;
-import java.io.IOException;
-import java.net.BindException;
-import java.net.InetSocketAddress;
-import java.net.MalformedURLException;
-import java.net.URL;
 import java.util.Properties;
+import java.util.concurrent.atomic.AtomicReference;
 
 @Component
 @Order(3)
@@ -59,14 +52,16 @@ public class EmbeddedKafkaServer implements Service {
     private static final String ATLAS_KAFKA_DATA          = "data";
     private static final int    MAX_RETRY_TO_ACQUIRE_PORT = 3;
 
-    private final boolean           isEmbedded;
-    private final Properties        properties;
-    private       KafkaServer       kafkaServer;
-    private       ServerCnxnFactory factory;
+    private final boolean       isEmbedded;
+    private final Configuration applicationProperties;
+    private final Properties    properties;
+
+    private KafkaClusterTestKit cluster;
 
     @Inject
     public EmbeddedKafkaServer(Configuration applicationProperties) {
-        Configuration kafkaConf = 
ApplicationProperties.getSubsetConfiguration(applicationProperties, 
PROPERTY_PREFIX);
+        this.applicationProperties = applicationProperties;
+        Configuration kafkaConf    = 
ApplicationProperties.getSubsetConfiguration(applicationProperties, 
PROPERTY_PREFIX);
 
         this.isEmbedded = applicationProperties.getBoolean(PROPERTY_EMBEDDED, 
false);
         this.properties = ConfigurationConverter.getProperties(kafkaConf);
@@ -78,8 +73,7 @@ public class EmbeddedKafkaServer implements Service {
 
         if (isEmbedded) {
             try {
-                startZk();
-                startKafka();
+                startKraftBroker();
             } catch (Exception e) {
                 throw new AtlasException("Failed to start embedded kafka", e);
             }
@@ -94,90 +88,152 @@ public class EmbeddedKafkaServer implements Service {
     public void stop() {
         LOG.info("==> EmbeddedKafkaServer.stop(isEmbedded={})", isEmbedded);
 
-        if (kafkaServer != null) {
-            kafkaServer.shutdown();
-        }
-
-        if (factory != null) {
-            factory.shutdown();
-        }
+        shutdownClusterQuietly();
 
         LOG.info("<== EmbeddedKafka.stop(isEmbedded={})", isEmbedded);
     }
 
-    private String startZk() throws IOException, InterruptedException {
-        String zkValue = properties.getProperty("zookeeper.connect");
+    private void startKraftBroker() throws Exception {
+        overrideExitMethods();
 
-        LOG.info("Starting zookeeper at {}", zkValue);
+        File   logDir                = constructDir("kafka");
+        String configuredBootstrap   = 
properties.getProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG);
 
-        URL  zkAddress   = getURL(zkValue);
-        File snapshotDir = constructDir("zk/txn");
-        File logDir      = constructDir("zk/snap");
+        LOG.info("Starting embedded KRaft kafka (log.dir={}, 
bootstrap.servers={})", logDir.getAbsolutePath(), configuredBootstrap);
 
-        for (int attemptCount = 0; attemptCount < MAX_RETRY_TO_ACQUIRE_PORT; 
attemptCount++) {
+        for (int attempt = 0; attempt < MAX_RETRY_TO_ACQUIRE_PORT; attempt++) {
             try {
-                factory = NIOServerCnxnFactory.createFactory(new 
InetSocketAddress(zkAddress.getHost(), zkAddress.getPort()), 1024);
-                break;
-            } catch (BindException e) {
-                LOG.warn("Attempt {}: Starting zookeeper at {} failed", 
attemptCount, zkValue);
+                startKraftBrokerOnce(logDir, configuredBootstrap);
+                return;
+            } catch (Exception e) {
+                LOG.warn("Attempt {}: failed to start embedded KRaft kafka", 
attempt, e);
+
+                shutdownClusterQuietly();
 
-                if (attemptCount == MAX_RETRY_TO_ACQUIRE_PORT - 1) {
+                if (attempt == MAX_RETRY_TO_ACQUIRE_PORT - 1) {
                     throw e;
                 }
 
-                
CommandHandlerUtility.tryKillingProcessUsingPort(zkAddress.getPort(), 
attemptCount != 0);
+                int port = parsePort(configuredBootstrap);
+
+                if (port > 0) {
+                    CommandHandlerUtility.tryKillingProcessUsingPort(port, 
attempt != 0);
+                }
             }
         }
+    }
+
+    private void startKraftBrokerOnce(File logDir, String configuredBootstrap) 
throws Exception {
+        KafkaClusterTestKit.Builder clusterBuilder = new 
KafkaClusterTestKit.Builder(
+                new TestKitNodes.Builder()
+                        .setCombined(true)
+                        .setNumBrokerNodes(1)
+                        .setNumControllerNodes(1)
+                        .build());
+
+        Properties brokerConfig = buildBrokerConfig(logDir, 
configuredBootstrap);
 
-        factory.startup(new ZooKeeperServer(snapshotDir, logDir, 500));
+        brokerConfig.forEach((key, value) -> 
clusterBuilder.setConfigProp(key.toString(), value));
 
-        String ret = factory.getLocalAddress().getAddress().toString();
+        cluster = clusterBuilder.build();
+        cluster.format();
+        cluster.startup();
+        cluster.waitForReadyBrokers();
 
-        LOG.info("Embedded zookeeper for Kafka started at {}", ret);
+        String bootstrapServers = StringUtils.isNotEmpty(configuredBootstrap)
+                ? configuredBootstrap
+                : 
cluster.clientProperties().get(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG).toString();
 
-        return ret;
+        LOG.info("Embedded KRaft kafka server started at {}", 
bootstrapServers);
+
+        applicationProperties.setProperty(PROPERTY_PREFIX + 
".bootstrap.servers", bootstrapServers);
+        properties.setProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, 
bootstrapServers);
     }
 
-    private void startKafka() throws IOException {
-        String kafkaValue = 
properties.getProperty(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG);
+    private Properties buildBrokerConfig(File logDir, String 
configuredBootstrap) {
+        Properties brokerConfig = new Properties();
 
-        LOG.info("Starting kafka at {}", kafkaValue);
+        brokerConfig.setProperty("log.dir", logDir.getAbsolutePath());
+        brokerConfig.setProperty("delete.topic.enable", "true");
+        brokerConfig.setProperty("auto.create.topics.enable", "true");
+        brokerConfig.setProperty("group.initial.rebalance.delay.ms", "0");
+        brokerConfig.setProperty("offsets.topic.replication.factor", "1");
+        brokerConfig.setProperty("transaction.state.log.replication.factor", 
"1");
+        brokerConfig.setProperty("transaction.state.log.min.isr", "1");
+        brokerConfig.setProperty("num.partitions", "1");
 
-        URL        kafkaAddress = getURL(kafkaValue);
-        Properties brokerConfig = properties;
+        String listener = toExternalListenerAddress(configuredBootstrap);
 
-        for (int attemptCount = 0; attemptCount < MAX_RETRY_TO_ACQUIRE_PORT; 
attemptCount++) {
-            try {
-                brokerConfig.setProperty("broker.id", "1");
-                brokerConfig.setProperty("host.name", kafkaAddress.getHost());
-                brokerConfig.setProperty("port", 
String.valueOf(kafkaAddress.getPort()));
-                brokerConfig.setProperty("log.dirs", 
constructDir("kafka").getAbsolutePath());
-                brokerConfig.setProperty("log.flush.interval.messages", 
String.valueOf(1));
+        if (listener != null) {
+            brokerConfig.setProperty("listeners", listener + 
",CONTROLLER://localhost:0");
+            brokerConfig.setProperty("advertised.listeners", listener);
+        }
 
-                kafkaServer = new 
KafkaServer(KafkaConfig.fromProps(brokerConfig), Time.SYSTEM, 
Option.apply(this.getClass().getName()), false);
+        String replicationFactor = 
properties.getProperty("offsets.topic.replication.factor");
 
-                kafkaServer.startup();
-                break;
-            } catch (KafkaException | ZooKeeperClientException e) {
-                LOG.warn("Attempt {}: kafka server with broker config {} 
failed", attemptCount, brokerConfig);
+        if (replicationFactor != null) {
+            brokerConfig.setProperty("offsets.topic.replication.factor", 
replicationFactor);
+        }
 
-                if (attemptCount == MAX_RETRY_TO_ACQUIRE_PORT - 1) {
-                    throw e;
-                }
+        return brokerConfig;
+    }
 
-                if (kafkaServer != null) {
-                    try {
-                        kafkaServer.shutdown();
-                    } catch (Exception ex) {
-                        LOG.info("Failed to shutdown kafka server", ex);
-                    }
-                }
+    private void shutdownClusterQuietly() {
+        if (cluster != null) {
+            AtomicReference<Throwable> shutdownFailure = new 
AtomicReference<>();
+
+            Utils.closeQuietly(cluster, "embedded Kafka cluster", 
shutdownFailure);
 
-                
CommandHandlerUtility.tryKillingProcessUsingPort(kafkaAddress.getPort(), 
attemptCount != 0);
+            if (shutdownFailure.get() != null) {
+                LOG.warn("Failed to shut down embedded Kafka cluster", 
shutdownFailure.get());
             }
+
+            cluster = null;
+        }
+    }
+
+    private static String toExternalListenerAddress(String bootstrapServers) {
+        if (StringUtils.isEmpty(bootstrapServers)) {
+            return null;
+        }
+
+        String hostPort = StringUtils.trim(bootstrapServers.split(",")[0]);
+
+        if (StringUtils.isEmpty(hostPort)) {
+            return null;
+        }
+
+        if (hostPort.contains("://")) {
+            return hostPort.replaceFirst("^PLAINTEXT://", "EXTERNAL://");
+        }
+
+        return "EXTERNAL://" + hostPort;
+    }
+
+    private static int parsePort(String bootstrapServers) {
+        if (StringUtils.isEmpty(bootstrapServers)) {
+            return -1;
         }
 
-        LOG.info("Embedded kafka server started with broker config {}", 
brokerConfig);
+        String hostPort = StringUtils.trim(bootstrapServers.split(",")[0]);
+        int    colon    = hostPort.lastIndexOf(':');
+
+        if (colon < 0) {
+            return -1;
+        }
+
+        try {
+            return Integer.parseInt(hostPort.substring(colon + 1));
+        } catch (NumberFormatException e) {
+            return -1;
+        }
+    }
+
+    private void overrideExitMethods() {
+        Exit.setExitProcedure((statusCode, message) ->
+                LOG.warn("Kafka Exit.exit({}, {}) suppressed in embedded 
broker", statusCode, message));
+        Exit.setHaltProcedure((statusCode, message) ->
+                LOG.warn("Kafka Exit.halt({}, {}) suppressed in embedded 
broker", statusCode, message));
     }
 
     private File constructDir(String dirPrefix) {
@@ -189,12 +245,4 @@ public class EmbeddedKafkaServer implements Service {
 
         return file;
     }
-
-    private URL getURL(String url) throws MalformedURLException {
-        try {
-            return new URL(url);
-        } catch (MalformedURLException e) {
-            return new URL("http://"; + url);
-        }
-    }
 }
diff --git 
a/notification/src/main/java/org/apache/atlas/kafka/KafkaNotification.java 
b/notification/src/main/java/org/apache/atlas/kafka/KafkaNotification.java
index 3c5ded145..e75ecfafd 100644
--- a/notification/src/main/java/org/apache/atlas/kafka/KafkaNotification.java
+++ b/notification/src/main/java/org/apache/atlas/kafka/KafkaNotification.java
@@ -174,9 +174,20 @@ public class KafkaNotification extends 
AbstractNotification implements Service {
     public void start() throws AtlasException {
         LOG.info("==> KafkaNotification.start()");
 
+        syncBootstrapServers();
+
         LOG.info("<== KafkaNotification.start()");
     }
 
+    private void syncBootstrapServers() throws AtlasException {
+        Configuration kafkaConf = 
ApplicationProperties.getSubsetConfiguration(ApplicationProperties.get(), 
PROPERTY_PREFIX);
+        String        bootstrap = kafkaConf.getString("bootstrap.servers");
+
+        if (StringUtils.isNotEmpty(bootstrap)) {
+            properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap);
+        }
+    }
+
     // ----- Service ---------------------------------------------------------
 
     @Override
diff --git a/pom.xml b/pom.xml
index c0f359387..80eab09f3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -130,8 +130,8 @@
         
<httpcomponents-httpclient.version>4.5.13</httpcomponents-httpclient.version>
         
<httpcomponents-httpcore.version>4.4.13</httpcomponents-httpcore.version>
         <ivy.version>2.5.2</ivy.version>
-        <jackson.databind.version>2.12.7</jackson.databind.version>
-        <jackson.version>2.12.7</jackson.version>
+        <jackson.databind.version>2.16.2</jackson.databind.version>
+        <jackson.version>2.16.2</jackson.version>
         <jacoco.version>0.8.13</jacoco.version>
         <janusgraph.cassandra.version>0.5.3</janusgraph.cassandra.version>
         <janusgraph.version>1.1.0</janusgraph.version>
@@ -149,8 +149,8 @@
         <json.version>3.2.11</json.version>
         <jsr.version>1.1</jsr.version>
         <junit.version>4.13.2</junit.version>
-        <kafka.scala.binary.version>2.12</kafka.scala.binary.version>
-        <kafka.version>2.8.2</kafka.version>
+        <kafka.scala.binary.version>2.13</kafka.scala.binary.version>
+        <kafka.version>3.9.1</kafka.version>
         <keycloak.version>6.0.1</keycloak.version>
         <logback.version>1.3.15</logback.version>
         <lucene-solr.version>8.11.3</lucene-solr.version>
@@ -1351,6 +1351,13 @@
                     <forkCount>0</forkCount>
                     <redirectTestOutputToFile>true</redirectTestOutputToFile>
                 </configuration>
+                <dependencies>
+                    <dependency>
+                        <groupId>org.apache.maven.surefire</groupId>
+                        <artifactId>surefire-testng</artifactId>
+                        <version>${failsafe.version}</version>
+                    </dependency>
+                </dependencies>
                 <executions>
                     <execution>
                         <id>integration-test</id>
diff --git a/webapp/pom.xml b/webapp/pom.xml
index f28f92c45..968fef620 100755
--- a/webapp/pom.xml
+++ b/webapp/pom.xml
@@ -58,6 +58,19 @@
             <version>${jackson.version}</version>
         </dependency>
 
+        <!-- Jackson JAX-RS 2.12.7: last line compatible with Jersey 1.19 / 
JAX-RS 1.1; core Jackson is 2.16.2 via ${jackson.version}. Full unification -> 
ATLAS-5190 -->
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-base</artifactId>
+            <version>2.12.7</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.jaxrs</groupId>
+            <artifactId>jackson-jaxrs-json-provider</artifactId>
+            <version>2.12.7</version>
+        </dependency>
+
         <dependency>
             <groupId>com.googlecode.json-simple</groupId>
             <artifactId>json-simple</artifactId>
@@ -124,6 +137,12 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+            <version>1.1.1</version>
+        </dependency>
+
         <dependency>
             <groupId>joda-time</groupId>
             <artifactId>joda-time</artifactId>
@@ -190,6 +209,16 @@
         <dependency>
             <groupId>org.apache.atlas</groupId>
             <artifactId>atlas-intg</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                    <artifactId>jackson-jaxrs-base</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>com.fasterxml.jackson.jaxrs</groupId>
+                    <artifactId>jackson-jaxrs-json-provider</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
@@ -370,7 +399,7 @@
                 </exclusion>
             </exclusions>
         </dependency>
-        <!-- PAM -->
+
         <dependency>
             <groupId>org.kohsuke</groupId>
             <artifactId>libpam4j</artifactId>
@@ -490,7 +519,7 @@
             <version>3.4</version>
             <scope>test</scope>
         </dependency>
-
+        <!-- PAM -->
         <dependency>
             <groupId>org.apache.atlas</groupId>
             <artifactId>atlas-testtools</artifactId>
diff --git 
a/webapp/src/test/java/org/apache/atlas/notification/ImportTaskListenerImplTest.java
 
b/webapp/src/test/java/org/apache/atlas/notification/ImportTaskListenerImplTest.java
index 9fb19960a..c0fdfddf5 100644
--- 
a/webapp/src/test/java/org/apache/atlas/notification/ImportTaskListenerImplTest.java
+++ 
b/webapp/src/test/java/org/apache/atlas/notification/ImportTaskListenerImplTest.java
@@ -117,7 +117,8 @@ public class ImportTaskListenerImplTest {
     }
 
     @AfterMethod
-    public void teardown() {
+    public void teardown() throws Exception {
+        shutdownImportExecutor(importTaskListener);
         Mockito.reset(asyncImportService, notificationHookConsumer, 
requestQueue, importRequest);
     }
 
@@ -295,6 +296,14 @@ public class ImportTaskListenerImplTest {
         Mockito.doReturn("import123").when(importRequest).getImportId();
         when(importRequest.getStatus()).thenReturn(WAITING);
         when(importRequest.getTopicName()).thenReturn("topic1");
+        
when(asyncImportService.fetchImportRequestByImportId("import123")).thenReturn(importRequest);
+
+        CountDownLatch consumerStarted = new CountDownLatch(1);
+
+        doAnswer(invocation -> {
+            consumerStarted.countDown();
+            return null;
+        }).when(notificationHookConsumer).startAsyncImportConsumer(any(), 
anyString(), anyString());
 
         ExecutorService realExecutor  = 
java.util.concurrent.Executors.newSingleThreadExecutor();
         Field           executorField = 
ImportTaskListenerImpl.class.getDeclaredField("executorService");
@@ -305,22 +314,21 @@ public class ImportTaskListenerImplTest {
 
         importTaskListener.onReceiveImportRequest(importRequest);
 
-        Thread.sleep(500);
-
-        verify(notificationHookConsumer, atLeastOnce())
-                
.startAsyncImportConsumer(NotificationInterface.NotificationType.ASYNC_IMPORT, 
"import123", "topic1");
+        assertTrue(consumerStarted.await(5, TimeUnit.SECONDS), 
"startAsyncImportConsumer was not invoked");
 
-        realExecutor.shutdownNow();
+        verify(notificationHookConsumer, 
times(1)).startAsyncImportConsumer(NotificationInterface.NotificationType.ASYNC_IMPORT,
 "import123", "topic1");
     }
 
     @Test
     public void testStartImportConsumer_Failure() throws Exception {
+        when(importRequest.getImportId()).thenReturn("import123");
         when(importRequest.getStatus()).thenReturn(WAITING);
         when(importRequest.getTopicName()).thenReturn("topic1");
+        
when(asyncImportService.fetchImportRequestByImportId("import123")).thenReturn(importRequest);
+
+        CountDownLatch consumerClosed = new CountDownLatch(1);
 
-        doThrow(new RuntimeException("Consumer failed"))
-                .when(notificationHookConsumer)
-                
.startAsyncImportConsumer(NotificationInterface.NotificationType.ASYNC_IMPORT, 
"import123", "topic1");
+        doThrow(new RuntimeException("Consumer 
failed")).when(notificationHookConsumer).startAsyncImportConsumer(NotificationInterface.NotificationType.ASYNC_IMPORT,
 "import123", "topic1");
 
         doAnswer(invocation -> {
             Object newStatus = invocation.getArgument(0);
@@ -328,6 +336,11 @@ public class ImportTaskListenerImplTest {
             return null;
         }).when(importRequest).setStatus(any());
 
+        doAnswer(invocation -> {
+            consumerClosed.countDown();
+            return null;
+        }).when(notificationHookConsumer).closeImportConsumer(anyString(), 
anyString());
+
         ExecutorService realExecutor  = 
java.util.concurrent.Executors.newSingleThreadExecutor();
         Field           executorField = 
ImportTaskListenerImpl.class.getDeclaredField("executorService");
 
@@ -338,12 +351,9 @@ public class ImportTaskListenerImplTest {
 
         importTaskListener.onReceiveImportRequest(importRequest);
 
-        Thread.sleep(500);
-
-        verify(notificationHookConsumer, times(1))
-                .closeImportConsumer("import123", "ATLAS_IMPORT_import123");
+        assertTrue(consumerClosed.await(5, TimeUnit.SECONDS), 
"closeImportConsumer was not invoked");
 
-        realExecutor.shutdownNow();
+        verify(notificationHookConsumer, 
times(1)).closeImportConsumer("import123", "ATLAS_IMPORT_import123");
     }
 
     @Test(dataProvider = "importQueueScenarios")
@@ -734,6 +744,20 @@ public class ImportTaskListenerImplTest {
         callers.shutdownNow();
     }
 
+    private void shutdownImportExecutor(ImportTaskListenerImpl listener) 
throws Exception {
+        if (listener == null) {
+            return;
+        }
+        Field executorField = 
ImportTaskListenerImpl.class.getDeclaredField("executorService");
+        executorField.setAccessible(true);
+        ExecutorService exec = (ExecutorService) executorField.get(listener);
+        if (exec != null) {
+            exec.shutdownNow();
+            exec.awaitTermination(5, TimeUnit.SECONDS);
+            executorField.set(listener, null);
+        }
+    }
+
     private void setExecutorServiceAndSemaphore(ImportTaskListenerImpl 
importTaskListener, ExecutorService mockExecutor, Semaphore mockSemaphore) {
         try {
             Field executorField = 
ImportTaskListenerImpl.class.getDeclaredField("executorService");
diff --git 
a/webapp/src/test/java/org/apache/atlas/web/integration/BasicSearchIT.java 
b/webapp/src/test/java/org/apache/atlas/web/integration/BasicSearchIT.java
index 24c7b1924..e89eeca82 100644
--- a/webapp/src/test/java/org/apache/atlas/web/integration/BasicSearchIT.java
+++ b/webapp/src/test/java/org/apache/atlas/web/integration/BasicSearchIT.java
@@ -57,7 +57,7 @@ import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
 public class BasicSearchIT extends BaseResourceIT {
-    private static final String IMPORTED_DATA_QUALIFIED_NAME_MARKER = "@cl1";
+    private static final String IMPORTED_DATA_QUALIFIED_NAME_MARKER = 
"default.";
 
     private AtlasUserSavedSearch userSavedSearch;
 
@@ -350,6 +350,10 @@ public class BasicSearchIT extends BaseResourceIT {
             return parameters;
         }
 
+        if (StringUtils.isNotBlank(parameters.getQuery()) || 
parameters.getOffset() > 0) {
+            return parameters;
+        }
+
         SearchParameters.FilterCriteria importedDataFilter = new 
SearchParameters.FilterCriteria();
 
         importedDataFilter.setAttributeName("qualifiedName");
diff --git 
a/webapp/src/test/java/org/apache/atlas/web/resources/AdminExportImportTestIT.java
 
b/webapp/src/test/java/org/apache/atlas/web/resources/AdminExportImportTestIT.java
index e2dd93919..f91947f68 100644
--- 
a/webapp/src/test/java/org/apache/atlas/web/resources/AdminExportImportTestIT.java
+++ 
b/webapp/src/test/java/org/apache/atlas/web/resources/AdminExportImportTestIT.java
@@ -105,7 +105,17 @@ public class AdminExportImportTestIT extends 
BaseResourceIT {
         try {
             performImport(FILE_TO_IMPORT, request, 32); // initial import has 
5 entities already in deleted state, hence current import will have 32 
processed-entities
         } catch (AtlasServiceException e) {
+            if (isConnectionClosedDuringTeardown(e)) {
+                LOG.warn("performTeardown: server connection closed during 
cleanup import, skipping.");
+                return;
+            }
             throw new SkipException("performTeardown: failed! Subsequent tests 
results may be affected.");
+        } catch (Exception e) {
+            if (isConnectionClosedDuringTeardown(e)) {
+                LOG.warn("performTeardown: server connection closed during 
cleanup import, skipping.");
+                return;
+            }
+            throw e;
         }
     }
 
@@ -146,4 +156,16 @@ public class AdminExportImportTestIT extends 
BaseResourceIT {
         assertNotNull(server.getAdditionalInfo());
         assertTrue(server.getAdditionalInfo().size() > 0);
     }
+
+    private boolean isConnectionClosedDuringTeardown(Throwable error) {
+        for (Throwable current = error; current != null; current = 
current.getCause()) {
+            if (current instanceof IOException && "Stream 
Closed".equals(current.getMessage())) {
+                return true;
+            }
+            if (current.getMessage() != null && 
current.getMessage().contains("Stream Closed")) {
+                return true;
+            }
+        }
+        return false;
+    }
 }


Reply via email to