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

wqliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-eventmesh.git


The following commit(s) were added to refs/heads/master by this push:
     new 93940d37f [ISSUE #3379] Add eventmesh-storage-pravega module
     new a6b0a0abb Merge pull request #3381 from xwm1992/add-storage-pravega
93940d37f is described below

commit 93940d37f9300580f54c13625a5b16a24d5124f9
Author: xwm1992 <[email protected]>
AuthorDate: Thu Mar 9 16:41:51 2023 +0800

    [ISSUE #3379] Add eventmesh-storage-pravega module
---
 .../eventmesh-storage-pravega/README.md            |  19 ++
 .../eventmesh-storage-pravega/build.gradle         |  39 ++++
 .../eventmesh-storage-pravega/gradle.properties    |  20 ++
 .../eventmesh-storage-pravega/pravega-storage.jpg  | Bin 0 -> 143684 bytes
 .../storage/pravega/PravegaConsumerImpl.java       | 109 ++++++++++
 .../storage/pravega/PravegaProducerImpl.java       | 121 +++++++++++
 .../pravega/PravegaStorageResourceServiceImpl.java |  37 ++++
 .../storage/pravega/client/PravegaClient.java      | 233 +++++++++++++++++++++
 .../pravega/client/PravegaCloudEventWriter.java    |  70 +++++++
 .../storage/pravega/client/PravegaEvent.java       |  77 +++++++
 .../storage/pravega/client/SubscribeTask.java      |  89 ++++++++
 .../pravega/config/PravegaStorageConfig.java       |  67 ++++++
 .../pravega/exception/PravegaStorageException.java |  31 +++
 .../org.apache.eventmesh.api.consumer.Consumer     |  16 ++
 .../org.apache.eventmesh.api.producer.Producer     |  16 ++
 ...he.eventmesh.api.storage.StorageResourceService |  16 ++
 .../src/main/resources/pravega-storage.properties  |  26 +++
 .../storage/pravega/client/PravegaClientTest.java  | 188 +++++++++++++++++
 .../pravega/config/PravegaStorageConfigTest.java   |  60 ++++++
 .../src/test/resources/pravega-storage.properties  |  26 +++
 settings.gradle                                    |   1 +
 21 files changed, 1261 insertions(+)

diff --git a/eventmesh-storage/eventmesh-storage-pravega/README.md 
b/eventmesh-storage/eventmesh-storage-pravega/README.md
new file mode 100644
index 000000000..92306965b
--- /dev/null
+++ b/eventmesh-storage/eventmesh-storage-pravega/README.md
@@ -0,0 +1,19 @@
+# Pravega Connector
+Pravega connector of EventMesh supports taking Pravega as event storage. 
[Pravega](https://cncf.pravega.io/) is a storage system that exposes Stream as 
the main primitive for continuous and unbounded data and meets the requirement 
of EventMesh connector module. 
+
+## The Design of Pravega Connector
+Pravega has 3 level storage concepts, which are **Event**, **Stream**, 
**Scope**. An **Event** is represented as a set of bytes within a Stream. A 
**Stream** is a durable, elastic, append-only, unbounded sequence of bytes 
having good performance and strong consistency. A **Scope** acts as a namespace 
for Stream names and all Stream names are unique within their Scope.
+
+Naturally, Pravega connector makes each topic correspond to a pravega stream. 
Because `EventStreamWriter` and `EventStreamReader` must assign to a special 
stream in a special scope, each topic corresponds to an `EventStreamWriter` and 
a `ReaderGroup`(only contains one `EventStreamReader`) as well. What's more, 
`SubscribeTask` thread will read every topic stream continuously.
+
+**Note**: 
+- Publish success will respond `-1` as messageId since writing to Pravega will 
not return message offset of the stream. If you want to use EventMesh with 
Pravega, please ensure messageId is unimportant.
+- Name and ID in Pravega must be `0-9`, `A-Z`, `a-z`, `.`, `-`.
+  - Topic must be legal in Pravega.
+  - ReaderGroup name format is `String.format("%s-%s", consumerGroup, topic)` 
for cluster message model, while random UUID for broadcast message model.
+  - Reader name format is `String.format("%s-reader", 
instanceName).replaceAll("\\(", "-").replaceAll("\\)", "-")`. The 
`instanceName` consists of `consumerGroup`, config `eventMesh.server.cluster`, 
EventMesh version and PID. 
+
+![pravega-storage](./pravega-storage.jpg)
+
+## Active Pravega Connector
+Referring to doc [_Build and Load 
Plugins_](https://eventmesh.apache.org/docs/installation/runtime#13-build-and-load-plugins)
 to load `:eventmesh-connector-plugin:eventmesh-connector-pravega` plugin 
module, and edit `eventMesh.connector.plugin.type=pravega` in config file 
`eventmesh.properties` to active pravega connector.
\ No newline at end of file
diff --git a/eventmesh-storage/eventmesh-storage-pravega/build.gradle 
b/eventmesh-storage/eventmesh-storage-pravega/build.gradle
new file mode 100644
index 000000000..f894a1120
--- /dev/null
+++ b/eventmesh-storage/eventmesh-storage-pravega/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+configurations {
+    implementation.exclude group: 'ch.qos.logback', module: 'logback-classic'
+    implementation.exclude group: 'log4j', module: 'log4j'
+}
+
+dependencies {
+    implementation project(":eventmesh-common")
+    implementation project(":eventmesh-storage:eventmesh-storage-api")
+    implementation("io.pravega:pravega-client:$pravega_version") {
+        exclude group: 'io.netty', module: 'netty-codec-http2:4.1.30.Final'
+    }
+    // use newer version to avoid CVE
+    implementation 'io.netty:netty-codec-http2'
+
+    testImplementation "org.testcontainers:testcontainers:1.17.3"
+
+    compileOnly 'org.projectlombok:lombok'
+    annotationProcessor 'org.projectlombok:lombok'
+
+    testCompileOnly 'org.projectlombok:lombok'
+    testAnnotationProcessor 'org.projectlombok:lombok'
+}
diff --git a/eventmesh-storage/eventmesh-storage-pravega/gradle.properties 
b/eventmesh-storage/eventmesh-storage-pravega/gradle.properties
new file mode 100644
index 000000000..0bd75ae16
--- /dev/null
+++ b/eventmesh-storage/eventmesh-storage-pravega/gradle.properties
@@ -0,0 +1,20 @@
+# 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.
+#
+
+pravega_version=0.11.0
+
+pluginType=storage
+pluginName=pravega
\ No newline at end of file
diff --git a/eventmesh-storage/eventmesh-storage-pravega/pravega-storage.jpg 
b/eventmesh-storage/eventmesh-storage-pravega/pravega-storage.jpg
new file mode 100644
index 000000000..20cb82960
Binary files /dev/null and 
b/eventmesh-storage/eventmesh-storage-pravega/pravega-storage.jpg differ
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaConsumerImpl.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaConsumerImpl.java
new file mode 100644
index 000000000..528be1302
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaConsumerImpl.java
@@ -0,0 +1,109 @@
+/*
+ * 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.pravega;
+
+import org.apache.eventmesh.api.AbstractContext;
+import org.apache.eventmesh.api.EventListener;
+import org.apache.eventmesh.api.consumer.Consumer;
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.storage.pravega.client.PravegaClient;
+import org.apache.eventmesh.storage.pravega.config.PravegaStorageConfig;
+import org.apache.eventmesh.storage.pravega.exception.PravegaStorageException;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.cloudevents.CloudEvent;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Config(field = "pravegaConnectorConfig")
+public class PravegaConsumerImpl implements Consumer {
+
+    private static final AtomicBoolean started = new AtomicBoolean(false);
+
+    /**
+     * Unified configuration class corresponding to pravega-storage.properties
+     */
+    private PravegaStorageConfig pravegaConnectorConfig;
+
+    private boolean isBroadcast;
+    private String instanceName;
+    private String consumerGroup;
+    private PravegaClient client;
+    private EventListener eventListener;
+
+    @Override
+    public void init(Properties keyValue) throws Exception {
+        isBroadcast = Boolean.parseBoolean(keyValue.getProperty("isBroadcast", 
"false"));
+        instanceName = keyValue.getProperty("instanceName", "");
+        consumerGroup = keyValue.getProperty("consumerGroup", "");
+
+        client = PravegaClient.getInstance(pravegaConnectorConfig);
+    }
+
+    @Override
+    public void start() {
+        started.compareAndSet(false, true);
+    }
+
+    @Override
+    public void shutdown() {
+        started.compareAndSet(true, false);
+    }
+
+    @Override
+    public boolean isStarted() {
+        return started.get();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return !started.get();
+    }
+
+    @Override
+    public void updateOffset(List<CloudEvent> cloudEvents, AbstractContext 
context) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void subscribe(String topic) throws Exception {
+        if (!client.subscribe(topic, isBroadcast, consumerGroup, instanceName, 
eventListener)) {
+            throw new PravegaStorageException(String.format("subscribe 
topic[%s] fail.", topic));
+        }
+    }
+
+    @Override
+    public void unsubscribe(String topic) {
+        if (!client.unsubscribe(topic, isBroadcast, consumerGroup)) {
+            throw new PravegaStorageException(String.format("unsubscribe 
topic[%s] fail.", topic));
+        }
+    }
+
+    @Override
+    public void registerEventListener(EventListener listener) {
+        this.eventListener = listener;
+    }
+
+    public PravegaStorageConfig getClientConfiguration() {
+        return this.pravegaConnectorConfig;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaProducerImpl.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaProducerImpl.java
new file mode 100644
index 000000000..03eef5b4e
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaProducerImpl.java
@@ -0,0 +1,121 @@
+/*
+ * 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.pravega;
+
+import org.apache.eventmesh.api.RequestReplyCallback;
+import org.apache.eventmesh.api.SendCallback;
+import org.apache.eventmesh.api.SendResult;
+import org.apache.eventmesh.api.exception.OnExceptionContext;
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+import org.apache.eventmesh.api.producer.Producer;
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.storage.pravega.client.PravegaClient;
+import org.apache.eventmesh.storage.pravega.config.PravegaStorageConfig;
+import org.apache.eventmesh.storage.pravega.exception.PravegaStorageException;
+
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.cloudevents.CloudEvent;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Config(field = "pravegaConnectorConfig")
+public class PravegaProducerImpl implements Producer {
+
+    private final AtomicBoolean started = new AtomicBoolean(false);
+    private PravegaClient client;
+
+    /**
+     * Unified configuration class corresponding to pravega-storage.properties
+     */
+    private PravegaStorageConfig pravegaConnectorConfig;
+
+    @Override
+    public void init(Properties properties) throws Exception {
+        client = PravegaClient.getInstance(pravegaConnectorConfig);
+    }
+
+    @Override
+    public void start() {
+        started.compareAndSet(false, true);
+    }
+
+    @Override
+    public void shutdown() {
+        started.compareAndSet(true, false);
+    }
+
+    @Override
+    public boolean isStarted() {
+        return started.get();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return !started.get();
+    }
+
+    @Override
+    public void publish(CloudEvent cloudEvent, SendCallback sendCallback) 
throws Exception {
+        try {
+            SendResult sendResult = client.publish(cloudEvent.getSubject(), 
cloudEvent);
+            sendCallback.onSuccess(sendResult);
+        } catch (Exception e) {
+            log.error("send message error, topic: {}", 
cloudEvent.getSubject());
+            OnExceptionContext onExceptionContext = 
OnExceptionContext.builder()
+                .messageId("-1")
+                .topic(cloudEvent.getSubject())
+                .exception(new StorageConnectorRuntimeException(e))
+                .build();
+            sendCallback.onException(onExceptionContext);
+        }
+    }
+
+    @Override
+    public void sendOneway(CloudEvent cloudEvent) {
+        client.publish(cloudEvent.getSubject(), cloudEvent);
+    }
+
+    @Override
+    public void request(CloudEvent cloudEvent, RequestReplyCallback 
rrCallback, long timeout) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public boolean reply(CloudEvent cloudEvent, SendCallback sendCallback) 
throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public void checkTopicExist(String topic) throws Exception {
+        if (!client.checkTopicExist(topic)) {
+            throw new PravegaStorageException(String.format("topic:%s is not 
exist", topic));
+        }
+    }
+
+    @Override
+    public void setExtFields() {
+
+    }
+
+    public PravegaStorageConfig getClientConfiguration() {
+        return this.pravegaConnectorConfig;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaStorageResourceServiceImpl.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaStorageResourceServiceImpl.java
new file mode 100644
index 000000000..ab8225c91
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/PravegaStorageResourceServiceImpl.java
@@ -0,0 +1,37 @@
+/*
+ * 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.pravega;
+
+import org.apache.eventmesh.api.storage.StorageResourceService;
+import org.apache.eventmesh.storage.pravega.client.PravegaClient;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PravegaStorageResourceServiceImpl implements 
StorageResourceService {
+
+    @Override
+    public void init() throws Exception {
+        PravegaClient.getInstance().start();
+    }
+
+    @Override
+    public void release() throws Exception {
+        PravegaClient.getInstance().shutdown();
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaClient.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaClient.java
new file mode 100644
index 000000000..7c0cf7829
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaClient.java
@@ -0,0 +1,233 @@
+/*
+ * 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.pravega.client;
+
+import org.apache.eventmesh.api.EventListener;
+import org.apache.eventmesh.api.SendResult;
+import org.apache.eventmesh.storage.pravega.config.PravegaStorageConfig;
+import org.apache.eventmesh.storage.pravega.exception.PravegaStorageException;
+
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import io.cloudevents.CloudEvent;
+import io.pravega.client.ClientConfig;
+import io.pravega.client.EventStreamClientFactory;
+import io.pravega.client.admin.ReaderGroupManager;
+import io.pravega.client.admin.StreamManager;
+import io.pravega.client.stream.EventStreamReader;
+import io.pravega.client.stream.EventStreamWriter;
+import io.pravega.client.stream.EventWriterConfig;
+import io.pravega.client.stream.ReaderConfig;
+import io.pravega.client.stream.ReaderGroupConfig;
+import io.pravega.client.stream.StreamConfiguration;
+import io.pravega.client.stream.impl.ByteArraySerializer;
+import io.pravega.shared.NameUtils;
+import io.pravega.shared.security.auth.DefaultCredentials;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class PravegaClient {
+
+    private final PravegaStorageConfig config;
+    private final StreamManager streamManager;
+    private final EventStreamClientFactory clientFactory;
+    private final ReaderGroupManager readerGroupManager;
+    private final Map<String, EventStreamWriter<byte[]>> writerMap = new 
ConcurrentHashMap<>();
+    private final Map<String, SubscribeTask> subscribeTaskMap = new 
ConcurrentHashMap<>();
+
+    private static PravegaClient instance;
+
+    public static PravegaClient getInstance() {
+        return instance;
+    }
+
+    public static PravegaClient getInstance(PravegaStorageConfig config) {
+        if (instance == null) {
+            instance = new PravegaClient(config);
+        }
+
+        return instance;
+    }
+
+    private PravegaClient(PravegaStorageConfig config) {
+        this.config = config;
+        streamManager = StreamManager.create(config.getControllerURI());
+        ClientConfig.ClientConfigBuilder clientConfigBuilder = 
ClientConfig.builder().controllerURI(config.getControllerURI());
+        if (config.isAuthEnabled()) {
+            clientConfigBuilder.credentials(new 
DefaultCredentials(config.getPassword(), config.getUsername()));
+        }
+        if (config.isTlsEnable()) {
+            
clientConfigBuilder.trustStore(config.getTruststore()).validateHostName(false);
+        }
+        ClientConfig clientConfig = clientConfigBuilder.build();
+        clientFactory = EventStreamClientFactory.withScope(config.getScope(), 
clientConfig);
+        readerGroupManager = ReaderGroupManager.withScope(config.getScope(), 
clientConfig);
+    }
+
+    protected static PravegaClient getNewInstance(PravegaStorageConfig config) 
{
+        return new PravegaClient(config);
+    }
+
+    public void start() {
+        if (createScope()) {
+            log.info("Create Pravega scope[{}] success.", config.getScope());
+        } else {
+            log.info("Pravega scope[{}] has already been created.", 
config.getScope());
+        }
+    }
+
+    public void shutdown() {
+        subscribeTaskMap.forEach((topic, task) -> task.stopRead());
+        subscribeTaskMap.clear();
+        writerMap.forEach((topic, writer) -> writer.close());
+        writerMap.clear();
+        readerGroupManager.close();
+        clientFactory.close();
+        streamManager.close();
+    }
+
+    /**
+     * Publish CloudEvent to Pravega stream named topic. Note that the 
messageId in SendResult is always -1 since {@link
+     * EventStreamWriter#writeEvent(Object)} just return {@link 
java.util.concurrent.CompletableFuture} with {@link Void} which couldn't get
+     * messageId.
+     *
+     * @param topic      topic
+     * @param cloudEvent cloudEvent
+     * @return SendResult whose messageId is always -1
+     */
+    public SendResult publish(String topic, CloudEvent cloudEvent) {
+        if (!createStream(topic)) {
+            log.debug("stream[{}] has already been created.", topic);
+        }
+        EventStreamWriter<byte[]> writer = writerMap.computeIfAbsent(topic, k 
-> createWrite(topic));
+        PravegaCloudEventWriter cloudEventWriter = new 
PravegaCloudEventWriter(topic);
+        PravegaEvent pravegaEvent = cloudEventWriter.writeBinary(cloudEvent);
+        try {
+            writer.writeEvent(PravegaEvent.toByteArray(pravegaEvent)).get(5, 
TimeUnit.SECONDS);
+        } catch (ExecutionException | InterruptedException | TimeoutException 
e) {
+            log.error(String.format("Write topic[%s] fail.", topic), e);
+            throw new PravegaStorageException(String.format("Write topic[%s] 
fail.", topic));
+        }
+        SendResult sendResult = new SendResult();
+        sendResult.setTopic(topic);
+        // set -1 as messageId since writeEvent method doesn't return it.
+        sendResult.setMessageId("-1");
+        return sendResult;
+
+    }
+
+    public boolean subscribe(String topic, boolean isBroadcast, String 
consumerGroup, String instanceName, EventListener listener) {
+        if (subscribeTaskMap.containsKey(topic)) {
+            return true;
+        }
+        String readerGroupName = buildReaderGroupName(isBroadcast, 
consumerGroup, topic);
+        createReaderGroup(topic, readerGroupName);
+        String readerId = buildReaderId(instanceName);
+        EventStreamReader<byte[]> reader = createReader(readerId, 
readerGroupName);
+        SubscribeTask subscribeTask = new SubscribeTask(topic, reader, 
listener);
+        subscribeTask.start();
+        subscribeTaskMap.put(topic, subscribeTask);
+        return true;
+    }
+
+    public boolean unsubscribe(String topic, boolean isBroadcast, String 
consumerGroup) {
+        if (!subscribeTaskMap.containsKey(topic)) {
+            return true;
+        }
+        if (!isBroadcast) {
+            deleteReaderGroup(buildReaderGroupName(false, consumerGroup, 
topic));
+        }
+        subscribeTaskMap.remove(topic).stopRead();
+        return true;
+    }
+
+    public boolean checkTopicExist(String topic) {
+        return streamManager.checkStreamExists(config.getScope(), topic);
+    }
+
+    private boolean createScope() {
+        return streamManager.createScope(config.getScope());
+    }
+
+    private boolean createStream(String topic) {
+        StreamConfiguration streamConfiguration = 
StreamConfiguration.builder().build();
+        return streamManager.createStream(config.getScope(), topic, 
streamConfiguration);
+    }
+
+    private EventStreamWriter<byte[]> createWrite(String topic) {
+        return clientFactory.createEventWriter(topic, new 
ByteArraySerializer(), EventWriterConfig.builder().build());
+    }
+
+    private String buildReaderGroupName(boolean isBroadcast, String 
consumerGroup, String topic) {
+        if (isBroadcast) {
+            return UUID.randomUUID().toString();
+        } else {
+            return String.format("%s-%s", consumerGroup, topic);
+        }
+    }
+
+    private String buildReaderId(String instanceName) {
+        return String.format("%s-reader", instanceName).replaceAll("\\(", 
"-").replaceAll("\\)", "-");
+    }
+
+    private void createReaderGroup(String topic, String readerGroupName) {
+        if (!checkTopicExist(topic)) {
+            createStream(topic);
+        }
+        ReaderGroupConfig readerGroupConfig =
+            ReaderGroupConfig.builder()
+                .stream(NameUtils.getScopedStreamName(config.getScope(), 
topic))
+                
.retentionType(ReaderGroupConfig.StreamDataRetention.AUTOMATIC_RELEASE_AT_LAST_CHECKPOINT)
+                .build();
+        readerGroupManager.createReaderGroup(readerGroupName, 
readerGroupConfig);
+    }
+
+    private void deleteReaderGroup(String readerGroup) {
+        readerGroupManager.deleteReaderGroup(readerGroup);
+    }
+
+    private EventStreamReader<byte[]> createReader(String readerId, String 
readerGroup) {
+        return clientFactory.createReader(readerId, readerGroup, new 
ByteArraySerializer(), ReaderConfig.builder().build());
+    }
+
+    protected StreamManager getStreamManager() {
+        return streamManager;
+    }
+
+    protected EventStreamClientFactory getClientFactory() {
+        return clientFactory;
+    }
+
+    protected ReaderGroupManager getReaderGroupManager() {
+        return readerGroupManager;
+    }
+
+    public Map<String, EventStreamWriter<byte[]>> getWriterMap() {
+        return writerMap;
+    }
+
+    protected Map<String, SubscribeTask> getSubscribeTaskMap() {
+        return subscribeTaskMap;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaCloudEventWriter.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaCloudEventWriter.java
new file mode 100644
index 000000000..ebc2c70e3
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaCloudEventWriter.java
@@ -0,0 +1,70 @@
+/*
+ * 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.pravega.client;
+
+import java.nio.charset.StandardCharsets;
+
+import io.cloudevents.CloudEventData;
+import io.cloudevents.SpecVersion;
+import io.cloudevents.core.format.EventFormat;
+import io.cloudevents.core.message.MessageWriter;
+import io.cloudevents.rw.CloudEventContextWriter;
+import io.cloudevents.rw.CloudEventRWException;
+import io.cloudevents.rw.CloudEventWriter;
+
+public class PravegaCloudEventWriter
+    implements MessageWriter<CloudEventWriter<PravegaEvent>, PravegaEvent>, 
CloudEventWriter<PravegaEvent> {
+
+    private final PravegaEvent pravegaEvent;
+
+    public PravegaCloudEventWriter(String topic) {
+        pravegaEvent = new PravegaEvent();
+        pravegaEvent.setTopic(topic);
+        pravegaEvent.setCreateTimestamp(System.currentTimeMillis());
+    }
+
+    @Override
+    public PravegaEvent setEvent(EventFormat format, byte[] value) throws 
CloudEventRWException {
+        pravegaEvent.setData(new String(value, StandardCharsets.UTF_8));
+        return pravegaEvent;
+    }
+
+    @Override
+    public PravegaEvent end(CloudEventData data) throws CloudEventRWException {
+        pravegaEvent.setData(new String(data.toBytes(), 
StandardCharsets.UTF_8));
+        return pravegaEvent;
+    }
+
+    @Override
+    public PravegaEvent end() throws CloudEventRWException {
+        pravegaEvent.setData("");
+        return pravegaEvent;
+    }
+
+    @Override
+    public CloudEventContextWriter withContextAttribute(String name, String 
value) throws CloudEventRWException {
+        pravegaEvent.getExtensions().put(name, value);
+        return this;
+    }
+
+    @Override
+    public CloudEventWriter<PravegaEvent> create(SpecVersion version) throws 
CloudEventRWException {
+        pravegaEvent.setVersion(version);
+        return this;
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaEvent.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaEvent.java
new file mode 100644
index 000000000..4ae85de92
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/PravegaEvent.java
@@ -0,0 +1,77 @@
+/*
+ * 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.pravega.client;
+
+import org.apache.eventmesh.common.utils.JsonUtils;
+import org.apache.eventmesh.storage.pravega.exception.PravegaStorageException;
+
+import java.io.Serializable;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.SpecVersion;
+import io.cloudevents.core.builder.CloudEventBuilder;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class PravegaEvent implements Serializable {
+
+    private static final long serialVersionUID = 0L;
+
+    private SpecVersion version;
+    private String topic;
+    private String data;
+    private Map<String, String> extensions = new HashMap<>();
+    private long createTimestamp;
+
+    public static byte[] toByteArray(PravegaEvent pravegaEvent) {
+        return 
JsonUtils.toJSONString(pravegaEvent).getBytes(StandardCharsets.UTF_8);
+    }
+
+    public static PravegaEvent getFromByteArray(byte[] body) {
+        return JsonUtils.parseObject(new String(body, StandardCharsets.UTF_8), 
PravegaEvent.class);
+    }
+
+    public CloudEvent convertToCloudEvent() {
+        CloudEventBuilder builder;
+        switch (version) {
+            case V03:
+                builder = CloudEventBuilder.v03();
+                break;
+            case V1:
+                builder = CloudEventBuilder.v1();
+                break;
+            default:
+                throw new PravegaStorageException(String.format("CloudEvent 
version %s does not support.", version));
+        }
+        builder.withData(data.getBytes(StandardCharsets.UTF_8))
+            .withId(extensions.remove("id"))
+            .withSource(URI.create(extensions.remove("source")))
+            .withType(extensions.remove("type"))
+            .withDataContentType(extensions.remove("datacontenttype"))
+            .withSubject(extensions.remove("subject"));
+        extensions.forEach(builder::withExtension);
+        return builder.build();
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/SubscribeTask.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/SubscribeTask.java
new file mode 100644
index 000000000..34ea6661b
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/client/SubscribeTask.java
@@ -0,0 +1,89 @@
+/*
+ * 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.pravega.client;
+
+import org.apache.eventmesh.api.EventListener;
+import org.apache.eventmesh.api.EventMeshAction;
+import org.apache.eventmesh.api.EventMeshAsyncConsumeContext;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import io.cloudevents.CloudEvent;
+import io.pravega.client.stream.EventRead;
+import io.pravega.client.stream.EventStreamReader;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SubscribeTask extends Thread {
+
+    private final EventStreamReader<byte[]> reader;
+    private final EventListener listener;
+    private final AtomicBoolean running = new AtomicBoolean(true);
+    private final AtomicBoolean continueRead = new AtomicBoolean(true);
+
+    public SubscribeTask(String name, EventStreamReader<byte[]> reader, 
EventListener listener) {
+        super(name);
+        this.reader = reader;
+        this.listener = listener;
+    }
+
+    @Override
+    public void run() {
+        CloudEvent cloudEvent = null;
+        while (running.get()) {
+            if (continueRead.get()) {
+                EventRead<byte[]> event = reader.readNextEvent(2000);
+                if (event == null) {
+                    continue;
+                }
+                byte[] eventByteArray = event.getEvent();
+                if (eventByteArray == null) {
+                    continue;
+                }
+                PravegaEvent pravegaEvent = 
PravegaEvent.getFromByteArray(eventByteArray);
+                cloudEvent = pravegaEvent.convertToCloudEvent();
+
+                listener.consume(cloudEvent, new 
PravegaEventMeshAsyncConsumeContext());
+            } else {
+                listener.consume(cloudEvent, new 
PravegaEventMeshAsyncConsumeContext());
+            }
+        }
+    }
+
+    public void stopRead() {
+        running.compareAndSet(true, false);
+    }
+
+    private class PravegaEventMeshAsyncConsumeContext extends 
EventMeshAsyncConsumeContext {
+
+        @Override
+        public void commit(EventMeshAction action) {
+            switch (action) {
+                case CommitMessage:
+                case ReconsumeLater:
+                    continueRead.set(false);
+                    break;
+                case ManualAck:
+                    continueRead.set(true);
+                    break;
+                default:
+            }
+        }
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfig.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfig.java
new file mode 100644
index 000000000..646d021f0
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfig.java
@@ -0,0 +1,67 @@
+/*
+ * 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.pravega.config;
+
+import org.apache.eventmesh.common.config.Config;
+import org.apache.eventmesh.common.config.ConfigFiled;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.net.URI;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+@Config(prefix = "eventMesh.server.pravega", path = 
"classPath://pravega-storage.properties")
+public class PravegaStorageConfig {
+
+    @ConfigFiled(field = "controller.uri")
+    private URI controllerURI = URI.create("tcp://127.0.0.1:9090");
+
+    @ConfigFiled(field = "scope")
+    private String scope = "eventmesh-pravega";
+
+    @ConfigFiled(field = "clientPool.size")
+    private int clientPoolSize = 8;
+
+    @ConfigFiled(field = "queue.size")
+    private int queueSize = 512;
+
+    @ConfigFiled(field = "authEnabled", reload = true)
+    private boolean authEnabled = false;
+
+    @ConfigFiled(field = "username")
+    private String username = "";
+
+    @ConfigFiled(field = "password")
+    private String password = "";
+
+    @ConfigFiled(field = "tlsEnabled")
+    private boolean tlsEnable = false;
+
+    @ConfigFiled(field = "truststore")
+    private String truststore = "";
+
+    public void reload() {
+        if (!authEnabled && StringUtils.isNotBlank(username) && 
StringUtils.isNotBlank(password)) {
+            authEnabled = true;
+        }
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/exception/PravegaStorageException.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/exception/PravegaStorageException.java
new file mode 100644
index 000000000..85cfa710e
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/java/org/apache/eventmesh/storage/pravega/exception/PravegaStorageException.java
@@ -0,0 +1,31 @@
+/*
+ * 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.pravega.exception;
+
+import org.apache.eventmesh.api.exception.StorageConnectorRuntimeException;
+
+public class PravegaStorageException extends StorageConnectorRuntimeException {
+
+    public PravegaStorageException(String message) {
+        super(message);
+    }
+
+    public PravegaStorageException(Throwable throwable) {
+        super(throwable);
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
new file mode 100644
index 000000000..2d68877d3
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.consumer.Consumer
@@ -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.
+
+pravega=org.apache.eventmesh.storage.pravega.PravegaConsumerImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
new file mode 100644
index 000000000..b35a24cd4
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.producer.Producer
@@ -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.
+
+pravega=org.apache.eventmesh.storage.pravega.PravegaProducerImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
new file mode 100644
index 000000000..64222b4dc
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/META-INF/eventmesh/org.apache.eventmesh.api.storage.StorageResourceService
@@ -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.
+
+pravega=org.apache.eventmesh.storage.pravega.PravegaStorageResourceServiceImpl
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/pravega-storage.properties
 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/pravega-storage.properties
new file mode 100644
index 000000000..25fa6f0af
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/main/resources/pravega-storage.properties
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+#######################pravega-client##################
+eventMesh.server.pravega.controller.uri=tcp://127.0.0.1:9090
+eventMesh.server.pravega.scope=eventmesh-pravega
+eventMesh.server.pravega.authEnabled=false
+eventMesh.server.pravega.username=
+eventMesh.server.pravega.password=
+eventMesh.server.pravega.tlsEnabled=false
+eventMesh.server.pravega.truststore=
+eventMesh.server.pravega.clientPool.size=8
+eventMesh.server.pravega.queue.size=512
\ No newline at end of file
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/client/PravegaClientTest.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/client/PravegaClientTest.java
new file mode 100644
index 000000000..594d1e0fd
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/client/PravegaClientTest.java
@@ -0,0 +1,188 @@
+/*
+ * 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.pravega.client;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.eventmesh.api.AsyncConsumeContext;
+import org.apache.eventmesh.api.EventListener;
+import org.apache.eventmesh.common.config.ConfigService;
+import org.apache.eventmesh.storage.pravega.config.PravegaStorageConfig;
+
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.function.Consumer;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import io.cloudevents.CloudEvent;
+import io.cloudevents.core.builder.CloudEventBuilder;
+import io.pravega.client.admin.StreamManager;
+import io.pravega.client.stream.ReaderGroupNotFoundException;
+
+import com.github.dockerjava.api.command.CreateContainerCmd;
+import com.github.dockerjava.api.model.PortBinding;
+
+@Ignore
+public class PravegaClientTest {
+
+    private StreamManager streamManager;
+    private PravegaStorageConfig config;
+    private URI controllerURI;
+
+    @Rule
+    public GenericContainer pravega =
+        new GenericContainer(DockerImageName.parse("pravega/pravega:0.11.0"))
+            .withExposedPorts(9090, 12345).withEnv("HOST_IP", 
"127.0.0.1").withCommand("standalone")
+            .withCreateContainerCmdModifier((Consumer<CreateContainerCmd>) 
createContainerCmd ->
+                createContainerCmd.getHostConfig()
+                    .withPortBindings(PortBinding.parse("9090:9090"), 
PortBinding.parse("12345:12345")));
+
+    @Before
+    public void setUp() {
+        String host = pravega.getHost();
+        int port = pravega.getFirstMappedPort();
+        controllerURI = URI.create(String.format("tcp://%s:%d", host, port));
+        streamManager = StreamManager.create(controllerURI);
+    }
+
+    @Test
+    public void startTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.start();
+        assertTrue(streamManager.checkScopeExists(config.getScope()));
+    }
+
+    @Test
+    public void shutdownTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.start();
+        pravegaClient.publish("test1", createCloudEvent());
+        pravegaClient.publish("test2", createCloudEvent());
+        pravegaClient.publish("test3", createCloudEvent());
+
+        pravegaClient.shutdown();
+        assertTrue(pravegaClient.getSubscribeTaskMap().isEmpty());
+        assertTrue(pravegaClient.getWriterMap().isEmpty());
+    }
+
+    @Test
+    public void publishTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.start();
+        pravegaClient.publish("test1", createCloudEvent());
+        pravegaClient.publish("test2", createCloudEvent());
+        assertTrue(streamManager.checkStreamExists(config.getScope(), 
"test1"));
+        assertTrue(streamManager.checkStreamExists(config.getScope(), 
"test2"));
+    }
+
+    @Test
+    public void subscribeTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.start();
+        pravegaClient.subscribe("test1", false, "consumerGroup", 
"instanceName", new EventListener() {
+            @Override
+            public void consume(CloudEvent cloudEvent, AsyncConsumeContext 
context) {
+                // do nothing
+            }
+        });
+        
assertNotNull(pravegaClient.getReaderGroupManager().getReaderGroup("test1-consumerGroup"));
+        assertTrue(pravegaClient.getSubscribeTaskMap().containsKey("test1"));
+    }
+
+    @Test
+    public void unsubscribeTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.unsubscribe("test1", false, "consumerGroup");
+
+        pravegaClient.start();
+        pravegaClient.subscribe("test1", false, "consumerGroup", 
"instanceName", new EventListener() {
+            @Override
+            public void consume(CloudEvent cloudEvent, AsyncConsumeContext 
context) {
+                // do nothing
+            }
+        });
+        pravegaClient.unsubscribe("test1", false, "consumerGroup");
+
+        assertFalse(pravegaClient.getSubscribeTaskMap().containsKey("test1"));
+        try {
+            
pravegaClient.getReaderGroupManager().getReaderGroup("test1-consumerGroup");
+        } catch (Exception e) {
+            assertTrue(e instanceof ReaderGroupNotFoundException);
+            return;
+        }
+        fail();
+    }
+
+    @Test
+    public void checkTopicExistTest() {
+        PravegaClient pravegaClient = getNewPravegaClient();
+        pravegaClient.start();
+        assertFalse(pravegaClient.checkTopicExist("test1"));
+
+        pravegaClient.publish("test1", createCloudEvent());
+        assertTrue(pravegaClient.checkTopicExist("test1"));
+    }
+
+    public PravegaClient getNewPravegaClient() {
+        ConfigService configService = ConfigService.getInstance();
+
+        this.config = 
configService.buildConfigInstance(PravegaStorageConfig.class);
+        this.config.setControllerURI(controllerURI);
+        return PravegaClient.getNewInstance(this.config);
+    }
+
+    private CloudEvent createCloudEvent() {
+        String data = 
"{\"headers\":{\"content-length\":\"36\",\"Accept\":\"*/*\",\"ip\":\"127.0.0.1:51226\",\"User-Agent\":\"curl/7.83.1\","
+            + 
"\"Host\":\"127.0.0.1:10105\",\"source\":\"127.0.0.1:51226\",\"Content-Type\":\"application/json\"},"
+            + 
"\"path\":\"/eventmesh/publish/TEST-TOPIC-HTTP-ASYNC\",\"method\":\"POST\",\"body\":{\"pass\":\"12345678\",\"name\":\"admin\"}}";
+        return CloudEventBuilder.v1()
+            .withId("c61039e1-7884-4d7f-b72f-6c61160a64fc")
+            .withSource(URI.create("source:127.0.0.1:51226"))
+            .withType("http_request")
+            .withDataContentType("application/json")
+            .withSubject("TEST-TOPIC-HTTP-ASYNC")
+            .withData(data.getBytes(StandardCharsets.UTF_8))
+            .withExtension("reqeventmesh2mqtimestamp", "1659342713460")
+            .withExtension("ip", "127.0.0.1:51226")
+            .withExtension("idc", "idc")
+            .withExtension("protocoldesc", "http")
+            .withExtension("pid", 2376)
+            .withExtension("env", "env")
+            .withExtension("sys", 1234)
+            .withExtension("ttl", 4000)
+            .withExtension("producergroup", "em-http-producer")
+            .withExtension("consumergroup", "em-http-consumer")
+            .withExtension("passwd", "pass")
+            .withExtension("bizseqno", "249695004068274968410952665702")
+            .withExtension("protocoltype", "http")
+            .withExtension("msgtype", "persistent")
+            .withExtension("uniqueid", "866012286651006371403062105469")
+            .withExtension("username", "eventmesh")
+            .withExtension("reqc2eventmeshtimestamp", "1659342713460")
+            .build();
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfigTest.java
 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfigTest.java
new file mode 100644
index 000000000..18fb7363f
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/java/org/apache/eventmesh/storage/pravega/config/PravegaStorageConfigTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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.pravega.config;
+
+import org.apache.eventmesh.api.factory.StoragePluginFactory;
+import org.apache.eventmesh.storage.pravega.PravegaConsumerImpl;
+import org.apache.eventmesh.storage.pravega.PravegaProducerImpl;
+
+import java.net.URI;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PravegaStorageConfigTest {
+
+    @Test
+    public void getConfigWhenPravegaConsumerInit() {
+        PravegaConsumerImpl consumer =
+            (PravegaConsumerImpl) 
StoragePluginFactory.getMeshMQPushConsumer("pravega");
+
+        PravegaStorageConfig config = consumer.getClientConfiguration();
+        assertConfig(config);
+    }
+
+    @Test
+    public void getConfigWhenPravegaProducerInit() {
+        PravegaProducerImpl producer =
+            (PravegaProducerImpl) 
StoragePluginFactory.getMeshMQProducer("pravega");
+
+        PravegaStorageConfig config = producer.getClientConfiguration();
+        assertConfig(config);
+    }
+
+    private void assertConfig(PravegaStorageConfig config) {
+        Assert.assertEquals(config.getControllerURI(), 
URI.create("tcp://127.0.0.1:816"));
+        Assert.assertEquals(config.getScope(), "scope-success!!!");
+        Assert.assertTrue(config.isAuthEnabled());
+        Assert.assertEquals(config.getUsername(), "username-success!!!");
+        Assert.assertEquals(config.getPassword(), "password-success!!!");
+        Assert.assertTrue(config.isTlsEnable());
+        Assert.assertEquals(config.getTruststore(), "truststore-success!!!");
+        Assert.assertEquals(config.getClientPoolSize(), 816);
+        Assert.assertEquals(config.getQueueSize(), 1816);
+    }
+}
diff --git 
a/eventmesh-storage/eventmesh-storage-pravega/src/test/resources/pravega-storage.properties
 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/resources/pravega-storage.properties
new file mode 100644
index 000000000..5bb5e5490
--- /dev/null
+++ 
b/eventmesh-storage/eventmesh-storage-pravega/src/test/resources/pravega-storage.properties
@@ -0,0 +1,26 @@
+#
+# 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.
+#
+#######################pravega-client##################
+eventMesh.server.pravega.controller.uri=tcp://127.0.0.1:816
+eventMesh.server.pravega.scope=scope-success!!!
+eventMesh.server.pravega.authEnabled=false
+eventMesh.server.pravega.username=username-success!!!
+eventMesh.server.pravega.password=password-success!!!
+eventMesh.server.pravega.tlsEnabled=true
+eventMesh.server.pravega.truststore=truststore-success!!!
+eventMesh.server.pravega.clientPool.size=816
+eventMesh.server.pravega.queue.size=1816
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 491c6a47e..fbce4a682 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -26,6 +26,7 @@ include 'eventmesh-spi'
 include 'eventmesh-storage:eventmesh-storage-api'
 include 'eventmesh-storage:eventmesh-storage-standalone'
 include 'eventmesh-storage:eventmesh-storage-kafka'
+include 'eventmesh-storage:eventmesh-storage-pravega'
 include 'eventmesh-storage:eventmesh-storage-redis'
 include 'eventmesh-storage:eventmesh-storage-rocketmq'
 include 'eventmesh-storage:eventmesh-storage-rabbitmq'


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to