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 =

Reply via email to