diff --git a/pom.xml b/pom.xml index 12cf56a..ec2f060 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ <qosdk.version>6.6.8</qosdk.version> <assertj.version>3.27.1</assertj.version> <spotless.version>2.43.0</spotless.version> + <chicory.version>1.0.0</chicory.version> </properties> <dependencyManagement> @@ -42,6 +43,13 @@ <type>pom</type> <scope>import</scope> </dependency> + <dependency> + <groupId>com.dylibso.chicory</groupId> + <artifactId>bom</artifactId> + <version>${chicory.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> </dependencies> </dependencyManagement> @@ -73,6 +81,14 @@ <artifactId>generator-annotations</artifactId> <version>${fabric8-client.version}</version> </dependency> + <dependency> + <groupId>com.dylibso.chicory</groupId> + <artifactId>runtime</artifactId> + </dependency> + <dependency> + <groupId>com.dylibso.chicory</groupId> + <artifactId>wasi</artifactId> + </dependency> <dependency> <groupId>org.awaitility</groupId> <artifactId>awaitility</artifactId> @@ -88,23 +104,6 @@ <groupId>io.quarkus</groupId> <artifactId>quarkus-container-image-jib</artifactId> </dependency> - <dependency> - <groupId>org.graalvm.polyglot</groupId> - <artifactId>polyglot</artifactId> - <version>${graalvm.version}</version> - </dependency> - <dependency> - <groupId>org.graalvm.js</groupId> - <artifactId>js-scriptengine</artifactId> - <version>${graalvm.version}</version> - </dependency> - <dependency> - <groupId>org.graalvm.polyglot</groupId> - <artifactId>js-community</artifactId> - <version>${graalvm.version}</version> - <type>pom</type> - <scope>runtime</scope> - </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk18on</artifactId> @@ -207,6 +206,23 @@ </executions> </plugin> + <plugin> + <groupId>com.dylibso.chicory</groupId> + <artifactId>aot-maven-plugin-experimental</artifactId> + <version>${chicory.version}</version> + <executions> + <execution> + <id>quickJs</id> + <goals> + <goal>wasm-aot-gen</goal> + </goals> + <configuration> + <name>io.javaoperatorsdk.operator.glue.wasm.QuickJs</name> + <wasmFile>src/main/resources/wasm/quickjs-provider.wasm</wasmFile> + </configuration> + </execution> + </executions> + </plugin> </plugins> </build> diff --git a/src/main/java/io/javaoperatorsdk/operator/glue/conditions/JavaScripCondition.java b/src/main/java/io/javaoperatorsdk/operator/glue/conditions/JavaScripCondition.java index 60c8ce4..a5bc608 100644 --- a/src/main/java/io/javaoperatorsdk/operator/glue/conditions/JavaScripCondition.java +++ b/src/main/java/io/javaoperatorsdk/operator/glue/conditions/JavaScripCondition.java @@ -1,12 +1,12 @@ package io.javaoperatorsdk.operator.glue.conditions; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; import java.util.Map; import java.util.stream.Collectors; -import javax.script.*; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,14 +17,20 @@ import io.javaoperatorsdk.operator.glue.GlueException; import io.javaoperatorsdk.operator.glue.Utils; import io.javaoperatorsdk.operator.glue.customresource.glue.Glue; +import io.javaoperatorsdk.operator.glue.wasm.QuickJsModule; import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition; +import com.dylibso.chicory.runtime.ImportValues; +import com.dylibso.chicory.runtime.Instance; +import com.dylibso.chicory.wasi.WasiOptions; +import com.dylibso.chicory.wasi.WasiPreview1; + +import static java.nio.charset.StandardCharsets.UTF_8; + public class JavaScripCondition implements Condition<GenericKubernetesResource, Glue> { private static final Logger LOG = LoggerFactory.getLogger(JavaScripCondition.class); - private static final String RESOURCE_AS_STRING_NAME_SUFFIX = "Str"; - private final String inputScript; public JavaScripCondition(String inputScript) { @@ -35,50 +41,71 @@ public JavaScripCondition(String inputScript) { public boolean isMet(DependentResource<GenericKubernetesResource, Glue> dependentResource, Glue glue, Context<Glue> context) { - try { + try (var jsStderr = new ByteArrayOutputStream(); + var wasi = WasiPreview1.builder() + .withOptions(WasiOptions.builder().withStderr(jsStderr).build()).build()) { var start = LocalDateTime.now(); - ScriptEngineManager manager = new ScriptEngineManager(); - ScriptEngine engine = manager.getEngineByName("js"); + + var quickjs = Instance.builder(QuickJsModule.load()) + .withImportValues(ImportValues.builder().addFunction(wasi.toHostFunctions()).build()) + .withMachineFactory(QuickJsModule::create) + .build(); StringBuilder finalScript = new StringBuilder(); - addTargetResourceToScript(dependentResource, glue, context, engine, finalScript); - addSecondaryResourceToScript(glue, context, engine, finalScript); + addTargetResourceToScript(dependentResource, glue, context, finalScript); + addSecondaryResourceToScript(glue, context, finalScript); finalScript.append("\n").append(inputScript); - LOG.debug("Final Condition JS:\n{}", finalScript); + // Using stderr to return the result + String finalJsCode = "console.error(eval(`" + finalScript + "`));"; + LOG.debug("Final Condition JS:\n{}", finalJsCode); + byte[] jsCode = finalJsCode.getBytes(UTF_8); + + var ptr = + quickjs.export("canonical_abi_realloc") + .apply( + 0, // original_ptr + 0, // original_size + 1, // alignment + jsCode.length // new size + )[0]; + + quickjs.memory().write((int) ptr, jsCode); + var aggregatedCodePtr = quickjs.export("compile_src").apply(ptr, jsCode.length)[0]; + + var codePtr = quickjs.memory().readI32((int) aggregatedCodePtr); // 32 bit + var codeLength = quickjs.memory().readU32((int) aggregatedCodePtr + 4); + + quickjs.export("eval_bytecode").apply(codePtr, codeLength); - CompiledScript script = ((Compilable) engine).compile(finalScript.toString()); - var res = (boolean) script.eval(); + var res = Boolean.valueOf(jsStderr.toString().trim()); LOG.debug("JS Condition evaluated as: {} within {}ms", res, ChronoUnit.MILLIS.between(start, LocalDateTime.now())); return res; - } catch (ScriptException e) { + } catch (IOException e) { throw new GlueException(e); } } private static void addSecondaryResourceToScript(Glue glue, Context<Glue> context, - ScriptEngine engine, StringBuilder finalScript) { + StringBuilder finalScript) { Map<String, String> namedSecondaryResources = nameAndSerializeSecondaryResources(context, glue); namedSecondaryResources.forEach((k, v) -> { - var stringKey = k + RESOURCE_AS_STRING_NAME_SUFFIX; - engine.put(stringKey, v); - finalScript.append("const ").append(k).append(" = JSON.parse(").append(stringKey) - .append(");\n"); + finalScript.append("const ").append(k).append(" = JSON.parse('").append(v) + .append("');\n"); }); } private static void addTargetResourceToScript( DependentResource<GenericKubernetesResource, Glue> dependentResource, Glue glue, - Context<Glue> context, ScriptEngine engine, StringBuilder finalScript) { + Context<Glue> context, StringBuilder finalScript) { var target = dependentResource.getSecondaryResource(glue, context); target.ifPresent(t -> { - engine.put("targetStr", Serialization.asJson(t)); - finalScript.append("const target = JSON.parse(targetStr);\n"); + finalScript.append("const target = JSON.parse('" + Serialization.asJson(t) + "');\n"); }); } diff --git a/src/main/resources/wasm/quickjs-provider.wasm b/src/main/resources/wasm/quickjs-provider.wasm new file mode 100644 index 0000000..abcdf4e Binary files /dev/null and b/src/main/resources/wasm/quickjs-provider.wasm differ diff --git a/src/test/java/io/javaoperatorsdk/operator/glue/JavaScripConditionTest.java b/src/test/java/io/javaoperatorsdk/operator/glue/JavaScripConditionTest.java index dcd1e15..37b4028 100644 --- a/src/test/java/io/javaoperatorsdk/operator/glue/JavaScripConditionTest.java +++ b/src/test/java/io/javaoperatorsdk/operator/glue/JavaScripConditionTest.java @@ -40,7 +40,7 @@ void setup() { void javaScriptSimpleConditionTest() { var condition = new JavaScripCondition(""" - x = 1; + const x = 1; x<2; """);