Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[demo] use QuickJs for the JavaScript provider #169

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 33 additions & 17 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -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>

Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down Expand Up @@ -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>

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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) {
Expand All @@ -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");
});
}

Expand Down
Binary file added src/main/resources/wasm/quickjs-provider.wasm
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void setup() {
void javaScriptSimpleConditionTest() {

var condition = new JavaScripCondition("""
x = 1;
const x = 1;
x<2;
""");

Expand Down
Loading