This is an automated email from the ASF dual-hosted git repository.
chenguangsheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/eventmesh.git
The following commit(s) were added to refs/heads/master by this push:
new 6dfd46d3e [ISSUE #4396] Realize the admin function for Kafka
storage-plugin (#4438)
6dfd46d3e is described below
commit 6dfd46d3ecd3a39fdb0929017ce9cec54eebf9c3
Author: Pil0tXia <[email protected]>
AuthorDate: Tue Sep 12 14:58:37 2023 +0800
[ISSUE #4396] Realize the admin function for Kafka storage-plugin (#4438)
* config: kafka-storage-plugin, RESTORE BEFORE MERGE
* fix: meshMqAdmin not loaded at startup
* feat: implement getTopic
* feat: get all history message count
* feat: implement createTopic
* optimize: remove redundant bootstrap.server config and add new configs
* feat: implement deleteTopic and handle exceptions
* Revert "config: kafka-storage-plugin, RESTORE BEFORE MERGE"
This reverts commit 20dce1af753c13cf447d33690d84231e624bac94.
* chore: move kafka comment to a more suitable place.
* optimize: support topic batch listing
---
.../eventmesh/storage/kafka/admin/KafkaAdmin.java | 150 +++++++++++++++++++++
.../storage/kafka/admin/KafkaAdminAdaptor.java | 85 ++++++++++++
.../storage/kafka/common/EventMeshConstants.java | 2 +
.../storage/kafka/config/ClientConfiguration.java | 8 ++
.../eventmesh/org.apache.eventmesh.api.admin.Admin | 16 +++
.../src/main/resources/kafka-client.properties | 20 ++-
6 files changed, 274 insertions(+), 7 deletions(-)
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdmin.java
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdmin.java
new file mode 100644
index 000000000..f86bfee87
--- /dev/null
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdmin.java
@@ -0,0 +1,150 @@
+/*
+ * 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.eventmesh.storage.kafka.admin;
+
+import static
org.apache.eventmesh.storage.kafka.common.EventMeshConstants.DEFAULT_TIMEOUT_IN_SECONDS;
+
+import org.apache.eventmesh.api.admin.AbstractAdmin;
+import org.apache.eventmesh.api.admin.TopicProperties;
+import org.apache.eventmesh.common.config.ConfigService;
+import org.apache.eventmesh.storage.kafka.config.ClientConfiguration;
+
+import org.apache.kafka.clients.admin.Admin;
+import org.apache.kafka.clients.admin.ListOffsetsResult.ListOffsetsResultInfo;
+import org.apache.kafka.clients.admin.NewTopic;
+import org.apache.kafka.clients.admin.OffsetSpec;
+import org.apache.kafka.clients.admin.TopicDescription;
+import org.apache.kafka.clients.consumer.ConsumerConfig;
+import org.apache.kafka.common.KafkaFuture;
+import org.apache.kafka.common.TopicPartition;
+import org.apache.kafka.common.TopicPartitionInfo;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class KafkaAdmin extends AbstractAdmin {
+
+ // properties for kafka admin client
+ private static final Properties kafkaProps = new Properties();
+
+ // properties for topic management
+ private static final Map<String, Integer> topicProps = new HashMap<>();
+
+ public KafkaAdmin() {
+ super(new AtomicBoolean(false));
+
+ ConfigService configService = ConfigService.getInstance();
+ ClientConfiguration clientConfiguration =
configService.buildConfigInstance(ClientConfiguration.class);
+
+ kafkaProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
clientConfiguration.getNamesrvAddr());
+ topicProps.put("partitionNum", clientConfiguration.getPartitions());
+ topicProps.put("replicationFactorNum", (int)
clientConfiguration.getReplicationFactors());
+ }
+
+ @Override
+ public List<TopicProperties> getTopic() {
+ try (Admin client = Admin.create(kafkaProps)) {
+ Set<String> topicList =
client.listTopics().names().get(DEFAULT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+ Map<String, KafkaFuture<TopicDescription>> topicDescriptionFutures
= client.describeTopics(topicList).values();
+ List<TopicProperties> result = new ArrayList<>();
+ for (Entry<String, KafkaFuture<TopicDescription>> entry :
topicDescriptionFutures.entrySet()) {
+ String topicName = entry.getKey();
+ TopicDescription topicDescription =
entry.getValue().get(DEFAULT_TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+ long messageCount = topicDescription.partitions().stream()
+ .mapToInt(TopicPartitionInfo::partition)
+ .mapToLong(partition -> {
+ try {
+ return getMsgCount(topicName, partition, client);
+ } catch (TimeoutException e) {
+ log.error("Failed to get msg offset when listing
topics. Kafka response timed out in {} seconds.",
+ DEFAULT_TIMEOUT_IN_SECONDS);
+ throw new RuntimeException(e);
+ } catch (ExecutionException | InterruptedException e) {
+ log.error("Failed to get msg offset when listing
topics.", e);
+ throw new RuntimeException(e);
+ }
+ })
+ .sum();
+
+ result.add(new TopicProperties(topicName, messageCount));
+ }
+ result.sort(Comparator.comparing(t -> t.name));
+ return result;
+ } catch (TimeoutException e) {
+ log.error("Failed to list topics. Kafka response timed out in {}
seconds.", DEFAULT_TIMEOUT_IN_SECONDS);
+ throw new RuntimeException(e);
+ } catch (Exception e) {
+ log.error("Failed to list topics.", e);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private long getMsgCount(String topicName, int partition, Admin client)
throws ExecutionException, InterruptedException, TimeoutException {
+ TopicPartition topicPartition = new TopicPartition(topicName,
partition);
+ long earliestOffset = getOffset(topicPartition, OffsetSpec.earliest(),
client);
+ long latestOffset = getOffset(topicPartition, OffsetSpec.latest(),
client);
+ return latestOffset - earliestOffset;
+ }
+
+ private long getOffset(TopicPartition topicPartition, OffsetSpec
offsetSpec, Admin client)
+ throws ExecutionException, InterruptedException, TimeoutException {
+ Map<TopicPartition, OffsetSpec> offsetSpecMap =
Collections.singletonMap(topicPartition, offsetSpec);
+ Map<TopicPartition, ListOffsetsResultInfo> offsetResultMap =
+
client.listOffsets(offsetSpecMap).all().get(DEFAULT_TIMEOUT_IN_SECONDS,
TimeUnit.SECONDS);
+ return offsetResultMap.get(topicPartition).offset();
+ }
+
+ @Override
+ public void createTopic(String topicName) {
+ try (Admin client = Admin.create(kafkaProps)) {
+ NewTopic newTopic = new NewTopic(topicName,
topicProps.get("partitionNum"),
topicProps.get("replicationFactorNum").shortValue());
+
client.createTopics(Collections.singletonList(newTopic)).all().get(DEFAULT_TIMEOUT_IN_SECONDS,
TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ log.error("Failed to create topic. Kafka response timed out in {}
seconds.", DEFAULT_TIMEOUT_IN_SECONDS);
+ } catch (Exception e) {
+ log.error("Failed to create topic.", e);
+ }
+ }
+
+ @Override
+ public void deleteTopic(String topicName) {
+ try (Admin client = Admin.create(kafkaProps)) {
+
client.deleteTopics(Collections.singletonList(topicName)).all().get(DEFAULT_TIMEOUT_IN_SECONDS,
TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ log.error("Failed to delete topic. Kafka response timed out in {}
seconds.", DEFAULT_TIMEOUT_IN_SECONDS);
+ } catch (Exception e) {
+ log.error("Failed to delete topic.", e);
+ }
+ }
+
+}
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdminAdaptor.java
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdminAdaptor.java
new file mode 100644
index 000000000..042756d45
--- /dev/null
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/admin/KafkaAdminAdaptor.java
@@ -0,0 +1,85 @@
+/*
+ * 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.eventmesh.storage.kafka.admin;
+
+import org.apache.eventmesh.api.admin.Admin;
+import org.apache.eventmesh.api.admin.TopicProperties;
+
+import java.util.List;
+import java.util.Properties;
+
+import io.cloudevents.CloudEvent;
+
+public class KafkaAdminAdaptor implements Admin {
+
+ private final KafkaAdmin admin;
+
+ public KafkaAdminAdaptor() {
+ admin = new KafkaAdmin();
+ }
+
+ @Override
+ public boolean isStarted() {
+ return admin.isStarted();
+ }
+
+ @Override
+ public boolean isClosed() {
+ return admin.isClosed();
+ }
+
+ @Override
+ public void start() {
+ admin.start();
+ }
+
+ @Override
+ public void shutdown() {
+ admin.shutdown();
+ }
+
+ @Override
+ public void init(Properties properties) throws Exception {
+ admin.init(properties);
+ }
+
+ @Override
+ public List<TopicProperties> getTopic() throws Exception {
+ return admin.getTopic();
+ }
+
+ @Override
+ public void createTopic(String topicName) throws Exception {
+ admin.createTopic(topicName);
+ }
+
+ @Override
+ public void deleteTopic(String topicName) throws Exception {
+ admin.deleteTopic(topicName);
+ }
+
+ @Override
+ public List<CloudEvent> getEvent(String topicName, int offset, int length)
throws Exception {
+ return admin.getEvent(topicName, offset, length);
+ }
+
+ @Override
+ public void publish(CloudEvent cloudEvent) throws Exception {
+ admin.publish(cloudEvent);
+ }
+}
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/common/EventMeshConstants.java
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/common/EventMeshConstants.java
index 2bc2f3c04..4b54846ce 100644
---
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/common/EventMeshConstants.java
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/common/EventMeshConstants.java
@@ -23,5 +23,7 @@ public class EventMeshConstants {
public static final int DEFAULT_TIMEOUT_IN_MILLISECONDS = 3000;
+ public static final int DEFAULT_TIMEOUT_IN_SECONDS = 10;
+
public static final String STORE_TIMESTAMP = "storetime";
}
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/config/ClientConfiguration.java
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/config/ClientConfiguration.java
index 3749e8157..2e92877ec 100644
---
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/config/ClientConfiguration.java
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/java/org/apache/eventmesh/storage/kafka/config/ClientConfiguration.java
@@ -44,6 +44,14 @@ public class ClientConfiguration {
@Builder.Default
private String clientPass = "password";
+ @ConfigFiled(field = "num.partitions")
+ @Builder.Default
+ private int partitions = 1;
+
+ @ConfigFiled(field = "num.replicationFactors")
+ @Builder.Default
+ private short replicationFactors = 1;
+
@ConfigFiled(field = "client.consumeThreadMin")
@Builder.Default
private Integer consumeThreadMin = 2;
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.admin.Admin
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.admin.Admin
new file mode 100644
index 000000000..9f8cb52ae
--- /dev/null
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.admin.Admin
@@ -0,0 +1,16 @@
+# 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.
+
+kafka=org.apache.eventmesh.storage.kafka.admin.KafkaAdminAdaptor
\ No newline at end of file
diff --git
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/kafka-client.properties
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/kafka-client.properties
index 31923d73d..cbe0a036f 100644
---
a/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/kafka-client.properties
+++
b/eventmesh-storage-plugin/eventmesh-storage-kafka/src/main/resources/kafka-client.properties
@@ -14,13 +14,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
-#######################kafka-client##################
-eventMesh.server.kafka.namesrvAddr=127.0.0.1:9092;127.0.0.1:9092
+
+# Configurations started with 'eventMesh.server.kafka' are required for
EventMesh.
+# You may see kafka.server.KafkaConfig for additional details and defaults of
the rest of the configurations.
+
+############################# EventMesh Kafka Client
#############################
+
+eventMesh.server.kafka.namesrvAddr=localhost:9092;localhost:9092
eventMesh.server.kafka.cluster=DefaultCluster
eventMesh.server.kafka.accessKey=********
eventMesh.server.kafka.secretKey=********
-# see kafka.server.KafkaConfig for additional details and defaults
+############################# EventMesh Log Basics
#############################
+
+# the number of partitions per topic
+eventMesh.server.kafka.num.partitions=1
+# the number of replication factors per topic
+eventMesh.server.kafka.num.replicationFactors=1
############################# Server Basics #############################
@@ -29,10 +39,6 @@ broker.id=0
############################# Socket Server Settings
#############################
-# The port the socket server listens on
-eventMesh.server.kafka.port=9092
-eventMesh.server.kafka.bootstrap.servers=localhost:9092
-
# Hostname the broker will bind to. If not set, the server will bind to all
interfaces
#host.name=localhost
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]