diff --git a/pulsar-broker/pom.xml b/pulsar-broker/pom.xml
index 1a24683ae4b87..3bcdd8260e341 100644
--- a/pulsar-broker/pom.xml
+++ b/pulsar-broker/pom.xml
@@ -111,7 +111,7 @@
pulsar-transaction-common
${project.version}
-
+
${project.groupId}
pulsar-io-batch-discovery-triggerers
@@ -145,7 +145,7 @@
io.dropwizard.metrics
metrics-core
-
+
org.xerial.snappy
@@ -158,7 +158,7 @@
${project.version}
test-jar
test
-
+
@@ -315,6 +315,7 @@
${project.groupId}
pulsar-functions-api-examples
${project.version}
+ pom
test
@@ -322,6 +323,7 @@
${project.groupId}
pulsar-io-batch-data-generator
${project.version}
+ pom
test
@@ -329,6 +331,7 @@
${project.groupId}
pulsar-io-data-generator
${project.version}
+ pom
test
@@ -407,49 +410,79 @@
- org.xolstice.maven.plugins
- protobuf-maven-plugin
- ${protobuf-maven-plugin.version}
-
- com.google.protobuf:protoc:${protoc3.version}:exe:${os.detected.classifier}
- true
-
+ maven-dependency-plugin
- generate-sources
+ copy-pulsar-io-connectors
+ generate-test-resources
- compile
- test-compile
+ copy
+
+
+
+ ${project.groupId}
+ pulsar-io-data-generator
+ ${project.version}
+ jar
+ true
+ ${project.build.directory}
+ pulsar-io-data-generator.nar
+
+
+ ${project.groupId}
+ pulsar-io-batch-data-generator
+ ${project.version}
+ jar
+ true
+ ${project.build.directory}
+ pulsar-io-batch-data-generator.nar
+
+
+ ${project.groupId}
+ pulsar-functions-api-examples
+ ${project.version}
+ jar
+ true
+ ${project.build.directory}
+ pulsar-functions-api-examples.jar
+
+
+
org.apache.maven.plugins
- maven-antrun-plugin
+ maven-surefire-plugin
+
+
+ ${project.build.directory}/pulsar-io-data-generator.nar
+ ${project.build.directory}/pulsar-functions-api-examples.jar
+ ${project.build.directory}/pulsar-io-batch-data-generator.nar
+
+
+
+
+
+ org.xolstice.maven.plugins
+ protobuf-maven-plugin
+ ${protobuf-maven-plugin.version}
+
+ com.google.protobuf:protoc:${protoc3.version}:exe:${os.detected.classifier}
+ true
+
- compile
+ generate-sources
- run
+ compile
+ test-compile
-
-
- copy test examples package
-
-
-
-
-
-
-
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/NamespacesTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/NamespacesTest.java
index 3dea0babf473e..f4260d46b42c5 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/NamespacesTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/NamespacesTest.java
@@ -148,6 +148,7 @@ public void initNamespace() throws Exception {
@Override
@BeforeMethod
public void setup() throws Exception {
+ resetConfig();
conf.setClusterName(testLocalCluster);
super.internalSetup();
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/v3/PackagesApiTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/v3/PackagesApiTest.java
index 2ab8c9d187d4d..9372263d4a7d2 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/v3/PackagesApiTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/broker/admin/v3/PackagesApiTest.java
@@ -63,8 +63,7 @@ public void testPackagesOperations() throws Exception {
admin.packages().upload(originalMetadata, packageName, file.getPath());
// testing download api
- String directory = file.getParent();
- String downloadPath = directory + "package-api-test-download.package";
+ String downloadPath = new File(file.getParentFile(), "package-api-test-download.package").getPath();
admin.packages().download(packageName, downloadPath);
File downloadFile = new File(downloadPath);
assertTrue(downloadFile.exists());
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/FileServer.java b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/FileServer.java
new file mode 100644
index 0000000000000..6d1fbdb3c3898
--- /dev/null
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/FileServer.java
@@ -0,0 +1,76 @@
+/**
+ * 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.pulsar.functions.worker;
+
+import com.google.api.Http;
+import com.sun.net.httpserver.Headers;
+import com.sun.net.httpserver.HttpServer;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Simple http server for serving files in Pulsar Function test cases
+ */
+@Slf4j
+public class FileServer implements AutoCloseable {
+ private final HttpServer httpServer;
+
+ public FileServer() throws IOException {
+ httpServer = HttpServer.create(new InetSocketAddress(0), 0);
+ // creates a default executor
+ httpServer.setExecutor(null);
+ }
+
+ public void serveFile(String path, File file) {
+ httpServer.createContext(path, he -> {
+ try {
+ Headers headers = he.getResponseHeaders();
+ headers.add("Content-Type", "application/octet-stream");
+
+ he.sendResponseHeaders(200, file.length());
+ try (OutputStream outputStream = he.getResponseBody()) {
+ Files.copy(file.toPath(), outputStream);
+ }
+ } catch (Exception e) {
+ log.error("Error serving file {} for path {}", file, path, e);
+ }
+ });
+ }
+
+ public void start() {
+ httpServer.start();
+ }
+
+ public void stop() {
+ httpServer.stop(0);
+ }
+
+ public String getUrl(String path) {
+ return "http://127.0.0.1:" + httpServer.getAddress().getPort() + path;
+ }
+
+ @Override
+ public void close() throws Exception {
+ stop();
+ }
+}
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionE2ESecurityTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionE2ESecurityTest.java
index bbfdccf4c9533..3b0628b75b025 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionE2ESecurityTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionE2ESecurityTest.java
@@ -21,6 +21,7 @@
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.apache.pulsar.broker.auth.MockedPulsarServiceBaseTest.retryStrategically;
import static org.apache.pulsar.functions.utils.functioncache.FunctionCacheEntry.JAVA_INSTANCE_JAR_PROPERTY;
+import static org.apache.pulsar.functions.worker.PulsarFunctionLocalRunTest.getPulsarApiExamplesJar;
import static org.mockito.Mockito.spy;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotEquals;
@@ -293,7 +294,7 @@ public void testAuthorizationWithAnonymousUser() throws Exception {
PulsarAdmin.builder().serviceHttpUrl(brokerServiceUrl).build())
) {
- String jarFilePathUrl = Utils.FILE + ":" + getClass().getClassLoader().getResource("pulsar-functions-api-examples.jar").getFile();
+ String jarFilePathUrl = getPulsarApiExamplesJar().toURI().toString();
FunctionConfig functionConfig = createFunctionConfig(TENANT, NAMESPACE, functionName,
sourceTopic, sinkTopic, subscriptionName);
@@ -563,7 +564,7 @@ public void testAuthorization() throws Exception {
PulsarAdmin.builder().serviceHttpUrl(brokerServiceUrl).authentication(authToken2).build())
) {
- String jarFilePathUrl = Utils.FILE + ":" + getClass().getClassLoader().getResource("pulsar-functions-api-examples.jar").getFile();
+ String jarFilePathUrl = getPulsarApiExamplesJar().toURI().toString();
FunctionConfig functionConfig = createFunctionConfig(TENANT, NAMESPACE, functionName,
sourceTopic, sinkTopic, subscriptionName);
diff --git a/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionLocalRunTest.java b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionLocalRunTest.java
index 9775f676587b6..7bb15bce1c3d2 100644
--- a/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionLocalRunTest.java
+++ b/pulsar-broker/src/test/java/org/apache/pulsar/functions/worker/PulsarFunctionLocalRunTest.java
@@ -27,24 +27,25 @@
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
-
-import java.io.BufferedInputStream;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
import java.io.File;
-import java.io.FileInputStream;
-import java.io.OutputStream;
+import java.io.IOException;
import java.lang.reflect.Method;
-import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
import java.net.URL;
+import java.net.URLClassLoader;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-
+import lombok.Cleanup;
import org.apache.commons.io.FileUtils;
import org.apache.pulsar.broker.PulsarService;
import org.apache.pulsar.broker.ServiceConfiguration;
@@ -70,6 +71,7 @@
import org.apache.pulsar.common.functions.Utils;
import org.apache.pulsar.common.io.SinkConfig;
import org.apache.pulsar.common.io.SourceConfig;
+import org.apache.pulsar.common.nar.NarClassLoader;
import org.apache.pulsar.common.policies.data.ClusterData;
import org.apache.pulsar.common.policies.data.SubscriptionStats;
import org.apache.pulsar.common.policies.data.TenantInfo;
@@ -77,24 +79,19 @@
import org.apache.pulsar.common.util.FutureUtil;
import org.apache.pulsar.common.util.ObjectMapperFactory;
import org.apache.pulsar.functions.LocalRunner;
-import org.apache.pulsar.functions.api.examples.pojo.AvroTestObject;
import org.apache.pulsar.functions.runtime.thread.ThreadRuntimeFactory;
import org.apache.pulsar.functions.runtime.thread.ThreadRuntimeFactoryConfig;
-import org.apache.pulsar.io.datagenerator.DataGeneratorPrintSink;
-import org.apache.pulsar.io.datagenerator.DataGeneratorSource;
import org.apache.pulsar.zookeeper.LocalBookkeeperEnsemble;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.sun.net.httpserver.Headers;
-import com.sun.net.httpserver.HttpServer;
-
/**
* Test Pulsar sink on function
*
@@ -122,14 +119,60 @@ public class PulsarFunctionLocalRunTest {
private final String TLS_CLIENT_KEY_FILE_PATH = "./src/test/resources/authentication/tls/client-key.pem";
private final String TLS_TRUST_CERT_FILE_PATH = "./src/test/resources/authentication/tls/cacert.pem";
+ private static final String SYSTEM_PROPERTY_NAME_NAR_FILE_PATH = "pulsar-io-data-generator.nar.path";
+
+ public static File getPulsarIODataGeneratorNar() {
+ return new File(Objects.requireNonNull(System.getProperty(SYSTEM_PROPERTY_NAME_NAR_FILE_PATH)
+ , "pulsar-io-data-generator.nar file location must be specified with "
+ + SYSTEM_PROPERTY_NAME_NAR_FILE_PATH + " system property"));
+ }
+
+ private static final String SYSTEM_PROPERTY_NAME_FUNCTIONS_API_EXAMPLES_JAR_FILE_PATH =
+ "pulsar-functions-api-examples.jar.path";
+
+ public static File getPulsarApiExamplesJar() {
+ return new File(Objects.requireNonNull(
+ System.getProperty(SYSTEM_PROPERTY_NAME_FUNCTIONS_API_EXAMPLES_JAR_FILE_PATH)
+ , "pulsar-functions-api-examples.jar file location must be specified with "
+ + SYSTEM_PROPERTY_NAME_FUNCTIONS_API_EXAMPLES_JAR_FILE_PATH + " system property"));
+ }
+
+ private static final String SYSTEM_PROPERTY_NAME_BATCH_NAR_FILE_PATH = "pulsar-io-batch-data-generator.nar.path";
+
+ public static File getPulsarIOBatchDataGeneratorNar() {
+ return new File(Objects.requireNonNull(System.getProperty(SYSTEM_PROPERTY_NAME_BATCH_NAR_FILE_PATH)
+ , "pulsar-io-batch-data-generator.nar file location must be specified with "
+ + SYSTEM_PROPERTY_NAME_BATCH_NAR_FILE_PATH + " system property"));
+ }
+
+
+ private URLClassLoader pulsarApiExamplesClassLoader;
+ private Class> avroTestObjectClass;
+
+
private static final Logger log = LoggerFactory.getLogger(PulsarFunctionLocalRunTest.class);
- private HttpServer fileServer;
+ private FileServer fileServer;
@DataProvider(name = "validRoleName")
public Object[][] validRoleName() {
return new Object[][] { { Boolean.TRUE }, { Boolean.FALSE } };
}
+ @BeforeClass
+ void loadPulsarApiExamples() throws MalformedURLException, ClassNotFoundException {
+ pulsarApiExamplesClassLoader = new URLClassLoader(new URL[]{getPulsarApiExamplesJar().toURI().toURL()},
+ Thread.currentThread().getContextClassLoader());
+ avroTestObjectClass = pulsarApiExamplesClassLoader.loadClass("org.apache.pulsar.functions.api.examples.pojo.AvroTestObject");
+ }
+
+ @AfterClass(alwaysRun = true)
+ void closeClassLoader() throws IOException {
+ if (pulsarApiExamplesClassLoader != null) {
+ pulsarApiExamplesClassLoader.close();
+ pulsarApiExamplesClassLoader = null;
+ }
+ }
+
@BeforeMethod
void setup(Method method) throws Exception {
@@ -190,7 +233,7 @@ void setup(Method method) throws Exception {
}
if (connectorsDir.mkdir()) {
- File file = new File(getClass().getClassLoader().getResource("pulsar-io-data-generator.nar").getFile());
+ File file = getPulsarIODataGeneratorNar();
Files.copy(file.toPath(), new File(connectorsDir.getAbsolutePath() + "/" + file.getName()).toPath());
} else {
throw new RuntimeException("Failed to create builtin connectors directory");
@@ -239,62 +282,16 @@ && isNotBlank(workerConfig.getBrokerClientAuthenticationParameters())) {
admin.tenants().createTenant(tenant, propAdmin);
// setting up simple web sever to test submitting function via URL
- fileServer = HttpServer.create(new InetSocketAddress(0), 0);
- fileServer.createContext("/pulsar-io-data-generator.nar", he -> {
- try {
-
- Headers headers = he.getResponseHeaders();
- headers.add("Content-Type", "application/octet-stream");
-
- File file = new File(getClass().getClassLoader().getResource("pulsar-io-data-generator.nar").getFile());
- byte[] bytes = new byte[(int) file.length()];
-
- FileInputStream fileInputStream = new FileInputStream(file);
- BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
- bufferedInputStream.read(bytes, 0, bytes.length);
-
- he.sendResponseHeaders(200, file.length());
- OutputStream outputStream = he.getResponseBody();
- outputStream.write(bytes, 0, bytes.length);
- outputStream.close();
-
- } catch (Exception e) {
- log.error("Error when downloading: {}", e, e);
- }
- });
- fileServer.createContext("/pulsar-functions-api-examples.jar", he -> {
- try {
-
- Headers headers = he.getResponseHeaders();
- headers.add("Content-Type", "application/octet-stream");
-
- File file = new File(
- getClass().getClassLoader().getResource("pulsar-functions-api-examples.jar").getFile());
- byte[] bytes = new byte[(int) file.length()];
-
- FileInputStream fileInputStream = new FileInputStream(file);
- BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
- bufferedInputStream.read(bytes, 0, bytes.length);
-
- he.sendResponseHeaders(200, file.length());
- OutputStream outputStream = he.getResponseBody();
- outputStream.write(bytes, 0, bytes.length);
- outputStream.close();
-
- } catch (Exception e) {
- log.error("Error when downloading: {}", e, e);
- }
- });
- fileServer.setExecutor(null); // creates a default executor
- log.info("Starting file server...");
+ fileServer = new FileServer();
+ fileServer.serveFile("/pulsar-io-data-generator.nar", getPulsarIODataGeneratorNar());
+ fileServer.serveFile("/pulsar-functions-api-examples.jar", getPulsarApiExamplesJar());
fileServer.start();
-
}
@AfterMethod(alwaysRun = true)
void shutdown() throws Exception {
log.info("--- Shutting down ---");
- fileServer.stop(0);
+ fileServer.stop();
pulsarClient.close();
admin.close();
pulsar.close();
@@ -415,6 +412,7 @@ private void testE2EPulsarFunctionLocalRun(String jarFilePathUrl) throws Excepti
functionConfig.setProcessingGuarantees(FunctionConfig.ProcessingGuarantees.ATLEAST_ONCE);
functionConfig.setJar(jarFilePathUrl);
+ @Cleanup
LocalRunner localRunner = LocalRunner.builder()
.functionConfig(functionConfig)
.clientAuthPlugin(AuthenticationTls.class.getName())
@@ -521,13 +519,13 @@ public void testAvroFunctionLocalRun(String jarFilePathUrl) throws Exception {
Schema schema = Schema.AVRO(SchemaDefinition.builder()
.withAlwaysAllowNull(true)
.withJSR310ConversionEnabled(true)
- .withPojo(AvroTestObject.class).build());
+ .withPojo(avroTestObjectClass).build());
//use AVRO schema
admin.schemas().createSchema(sourceTopic, schema.getSchemaInfo());
// please note that in this test the sink topic schema is different from the schema of the source topic
//produce message to sourceTopic
- Producer producer = pulsarClient.newProducer(schema).topic(sourceTopic).create();
+ Producer