This is an automated email from the ASF dual-hosted git repository.
rong pushed a commit to branch test-4
in repository https://gitbox.apache.org/repos/asf/iotdb.git
The following commit(s) were added to refs/heads/test-4 by this push:
new 6e2927d2c74 add validate call check before udf creation
6e2927d2c74 is described below
commit 6e2927d2c74d4463b5396a6af1d755372f3845cd
Author: Steve Yurong Su <[email protected]>
AuthorDate: Fri Jun 20 10:55:39 2025 +0800
add validate call check before udf creation
---
.../main/java/org/apache/iotdb/udf/api/UDF.java | 15 ++++++---
.../iotdb/confignode/manager/UDFManager.java | 38 ++++++++++++++++++++++
2 files changed, 49 insertions(+), 4 deletions(-)
diff --git a/iotdb-api/udf-api/src/main/java/org/apache/iotdb/udf/api/UDF.java
b/iotdb-api/udf-api/src/main/java/org/apache/iotdb/udf/api/UDF.java
index 3fa7d36f74f..6e50c2f397b 100644
--- a/iotdb-api/udf-api/src/main/java/org/apache/iotdb/udf/api/UDF.java
+++ b/iotdb-api/udf-api/src/main/java/org/apache/iotdb/udf/api/UDF.java
@@ -27,6 +27,17 @@ import java.util.Optional;
public interface UDF {
+ /**
+ * This method is mainly used to validate the UDF itself, such as checking
if the UDF is properly
+ * configured or if it has all the required dependencies.
+ *
+ * @return an {@link Optional} containing an {@link Exception} if validation
fails, otherwise
+ * returns an empty {@link Optional}
+ */
+ default Optional<Exception> validate() {
+ return Optional.empty();
+ }
+
/**
* This method is mainly used to validate {@link UDFParameters} and it is
executed before {@link
* UDTF#beforeStart(UDFParameters, UDTFConfigurations)} is called.
@@ -39,10 +50,6 @@ public interface UDF {
// do nothing
}
- default Optional<Exception> check() {
- return Optional.empty();
- }
-
/** This method is mainly used to release the resources used in the UDF. */
default void beforeDestroy() {
// do nothing
diff --git
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/UDFManager.java
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/UDFManager.java
index 00ed020a14e..d6d6e014698 100644
---
a/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/UDFManager.java
+++
b/iotdb-core/confignode/src/main/java/org/apache/iotdb/confignode/manager/UDFManager.java
@@ -23,6 +23,8 @@ import org.apache.iotdb.common.rpc.thrift.TDataNodeLocation;
import org.apache.iotdb.common.rpc.thrift.TSStatus;
import org.apache.iotdb.commons.conf.IoTDBConstant;
import org.apache.iotdb.commons.udf.UDFInformation;
+import org.apache.iotdb.commons.udf.service.UDFClassLoader;
+import org.apache.iotdb.commons.udf.service.UDFExecutableManager;
import org.apache.iotdb.confignode.client.async.CnToDnAsyncRequestType;
import
org.apache.iotdb.confignode.client.async.CnToDnInternalServiceAsyncRequestManager;
import
org.apache.iotdb.confignode.client.async.handlers.DataNodeAsyncRequestContext;
@@ -43,6 +45,7 @@ import
org.apache.iotdb.mpp.rpc.thrift.TCreateFunctionInstanceReq;
import org.apache.iotdb.mpp.rpc.thrift.TDropFunctionInstanceReq;
import org.apache.iotdb.rpc.RpcUtils;
import org.apache.iotdb.rpc.TSStatusCode;
+import org.apache.iotdb.udf.api.UDF;
import org.apache.tsfile.utils.Binary;
import org.slf4j.Logger;
@@ -89,6 +92,13 @@ public class UDFManager {
new UDFInformation(udfName, req.getClassName(), false, isUsingURI,
jarName, jarMD5);
final boolean needToSaveJar = isUsingURI &&
udfInfo.needToSaveJar(jarName);
+ final TSStatus status =
sandboxValidationBeforeCreateFunction(udfInformation);
+ if (status.getCode() != TSStatusCode.SUCCESS_STATUS.getStatusCode()) {
+ LOGGER.warn(
+ "Sandbox validation failed for UDF [{}], reason: {}", udfName,
status.getMessage());
+ return status;
+ }
+
LOGGER.info(
"Start to create UDF [{}] on Data Nodes, needToSaveJar[{}]",
udfName, needToSaveJar);
@@ -121,6 +131,34 @@ public class UDFManager {
}
}
+ private TSStatus sandboxValidationBeforeCreateFunction(final UDFInformation
udfInformation) {
+ try (final UDFClassLoader classLoader =
+ new UDFClassLoader(UDFExecutableManager.getInstance().getLibRoot())) {
+ // ensure that jar file contains the class and the class is a UDF
+ final Class<?> clazz = Class.forName(udfInformation.getClassName(),
true, classLoader);
+ final UDF udf = (UDF) clazz.getDeclaredConstructor().newInstance();
+ return udf.validate()
+ .map(
+ e ->
+ RpcUtils.getStatus(
+ TSStatusCode.UDF_LOAD_CLASS_ERROR,
+ String.format(
+ "Fail to validate UDF class [%s] for function [%s]
in sandbox, reason: %s",
+ udfInformation.getClassName(),
+ udfInformation.getFunctionName(),
+ e.getMessage())))
+ .orElse(RpcUtils.SUCCESS_STATUS);
+ } catch (final Throwable throwable) {
+ return RpcUtils.getStatus(
+ TSStatusCode.UDF_LOAD_CLASS_ERROR,
+ String.format(
+ "Fail to validate UDF class [%s] for function [%s] in sandbox,
reason: %s",
+ udfInformation.getClassName(),
+ udfInformation.getFunctionName(),
+ throwable.getMessage()));
+ }
+ }
+
private List<TSStatus> createFunctionOnDataNodes(UDFInformation
udfInformation, byte[] jarFile)
throws IOException {
final Map<Integer, TDataNodeLocation> dataNodeLocationMap =