Skip to content

Commit 630f506

Browse files
destifomichael-0acf4Yohe-Am
authored
feat(sdk, gate, cli): upload protocol poc uploading wasm file for WasmEdge Runtime for single replica mode (#631)
Upload protocol for wasm files and atrifacts for `WasmEdge Runtime` for single replica mode #### Motivation and context - Upload WasmEdge Runtime artifacts during typegraph deploy - Access and load WasmEdge Runtime artifacts from the local file system from typegate #### Migration notes *No Migrations Needed* ### Checklist - [x] The change come with new or modified tests - [ ] Hard-to-understand functions have explanatory comments - [ ] End-user documentation is updated to reflect the change --------- Signed-off-by: Estifanos Bireda <[email protected]> Co-authored-by: afmika <[email protected]> Co-authored-by: Yohe-Am <[email protected]>
1 parent 70b69d3 commit 630f506

File tree

40 files changed

+630
-112
lines changed

40 files changed

+630
-112
lines changed

.github/workflows/tests.yml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ env:
1313
DENO_VERSION: "1.41.0"
1414
RUST_BACKTRACE: "full"
1515
RUST_LOG: "info,swc_ecma_codegen=off,tracing::span=off"
16+
DENO_DIR: deno-dir
1617

1718
jobs:
1819
changes:
@@ -151,7 +152,7 @@ jobs:
151152
# temporary fix
152153
cache-key-prefix: ${{ matrix.os }}
153154
- shell: bash
154-
env:
155+
env:
155156
WASM_FILE: target/debug/typegraph_core.wasm
156157
run: |
157158
python3 -m venv .venv
@@ -184,6 +185,8 @@ jobs:
184185
include:
185186
- platform: linux/amd64
186187
runner: ubuntu-latest
188+
# - platform: linux/amd64
189+
# runner: custom-ubuntu-large
187190
# FIXME: try macos-14 runner once all actions support it
188191
# docker buildx action broken as of 2024-02-09
189192

@@ -290,14 +293,14 @@ jobs:
290293
path: .venv
291294
key: ${{ runner.os }}-venv-${{ hashFiles('**/poetry.lock', '.ghjk/lock.json') }}
292295
# FIXME: the custom runner is missing some ambient items found in
293-
# github runner images by default. remove this hack when ghjk handles
296+
# github runner images by default. remove this hack when ghjk handles
294297
# zstd/bsdtar on it's own
295298
- run: |
296299
sudo apt update
297300
sudo apt install -y --no-install-recommends zstd
298301
- uses: metatypedev/setup-ghjk@a7bbf22959e3c0f80b8ba9f800b0a9f1ff17fa7b
299302
- shell: bash
300-
env:
303+
env:
301304
WASM_FILE: target/debug/typegraph_core.wasm
302305
run: |
303306
echo pwd in $(pwd)
@@ -345,6 +348,7 @@ jobs:
345348
deno cache --import-map typegate/import_map.json \
346349
typegate/src/main.ts \
347350
typegate/tests/utils/*.ts \
351+
typegate/tests/runtimes/wasmedge/*.ts \
348352
dev/deps.ts \
349353
dev/utils.ts
350354
deno --unstable-worker-options --unstable-net coverage ./coverage --lcov > coverage.lcov

docs/workflows/upload_protocol.svg

Lines changed: 22 additions & 0 deletions
Loading

libs/common/src/typegraph/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ pub mod visitor;
1010

1111
pub use types::*;
1212

13+
use std::collections::HashMap;
1314
use std::path::{Path, PathBuf};
1415
use std::sync::Arc;
1516

@@ -99,6 +100,7 @@ pub struct TypeMeta {
99100
pub rate: Option<Rate>,
100101
pub version: String,
101102
pub random_seed: Option<u32>,
103+
pub ref_artifacts: HashMap<String, PathBuf>,
102104
}
103105

104106
#[cfg_attr(feature = "codegen", derive(JsonSchema))]

libs/common/src/typegraph/runtimes/wasmedge.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use serde::{Deserialize, Serialize};
1010
pub struct WasiMatData {
1111
pub wasm: String,
1212
pub func: String,
13+
pub artifact_hash: String,
14+
pub tg_name: Option<String>,
1315
}
1416

1517
#[cfg_attr(feature = "codegen", derive(JsonSchema))]

typegate/engine/src/runtimes/wasmedge.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ use anyhow::bail;
1010
#[rustfmt::skip]
1111
use deno_core as deno_core; // necessary for re-exported macros to work
1212

13-
use base64::{engine::general_purpose, Engine as _};
1413
use wasmedge_sdk::VmBuilder;
1514
use wasmedge_sdk::{
1615
config::{ConfigBuilder, HostRegistrationConfigOptions},
1716
dock::{Param, VmDock},
1817
Module,
1918
};
2019

20+
use std::{env, fs};
21+
2122
#[derive(Deserialize)]
2223
#[serde(crate = "serde")]
2324
pub struct WasiInput {
@@ -54,9 +55,14 @@ fn param_cast(out: &str, res: &mut Vec<Box<dyn Any + Send + Sync>>) -> Result<St
5455
pub fn op_wasmedge_wasi(#[serde] input: WasiInput) -> Result<String> {
5556
// https://github.com/second-state/wasmedge-rustsdk-examples
5657

57-
let bytes = general_purpose::STANDARD
58-
.decode(input.wasm.as_bytes())
59-
.unwrap();
58+
let wasm_relative_path = PathBuf::from(input.wasm);
59+
60+
let wasm_absolute_path = match env::current_dir() {
61+
Ok(cwd) => cwd.join(wasm_relative_path),
62+
Err(e) => return Err(anyhow::anyhow!(e)),
63+
};
64+
65+
let bytes = fs::read(wasm_absolute_path).unwrap();
6066
let module = Module::from_bytes(None, bytes).unwrap();
6167

6268
let config = ConfigBuilder::default()

typegate/src/runtimes/wasmedge.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { Resolver, RuntimeInitParams } from "../types.ts";
77
import { nativeResult } from "../utils.ts";
88
import { ComputeStage } from "../engine/query_engine.ts";
99
import { registerRuntime } from "./mod.ts";
10+
import config from "../config.ts";
1011

1112
@registerRuntime("wasmedge")
1213
export class WasmEdgeRuntime extends Runtime {
@@ -29,7 +30,7 @@ export class WasmEdgeRuntime extends Runtime {
2930
_verbose: boolean,
3031
): ComputeStage[] {
3132
const { materializer, argumentTypes, outType } = stage.props;
32-
const { wasm, func } = materializer?.data ?? {};
33+
const { wasm, func, artifact_hash, tg_name } = materializer?.data ?? {};
3334
const order = Object.keys(argumentTypes ?? {});
3435

3536
// always wasi
@@ -40,7 +41,8 @@ export class WasmEdgeRuntime extends Runtime {
4041
await native.wasmedge_wasi(
4142
{
4243
func: func as string,
43-
wasm: wasm as string,
44+
wasm:
45+
`${config.tmp_dir}/metatype_artifacts/${tg_name as string}/artifacts/${wasm as string}.${artifact_hash as string}`,
4446
args: transfert,
4547
out: outType.type,
4648
},
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright Metatype OÜ, licensed under the Elastic License 2.0.
2+
// SPDX-License-Identifier: Elastic-2.0
3+
4+
import config from "../config.ts";
5+
import { signJWT, verifyJWT } from "../crypto.ts";
6+
import { UploadUrlMeta } from "../typegate/mod.ts";
7+
8+
interface TypegraphArtifact {
9+
name: string;
10+
artifact_hash: string;
11+
artifact_size_in_bytes: number;
12+
}
13+
14+
function createUploadPath(origin: string, typegraphName: string) {
15+
const rand_path = crypto.randomUUID();
16+
return `${origin}/${typegraphName}/upload-artifacts/artifacts/${rand_path}`;
17+
}
18+
19+
export async function handleUploadUrl(
20+
request: Request,
21+
tgName: string,
22+
urlCache: Map<string, UploadUrlMeta>,
23+
) {
24+
const url = new URL(request.url);
25+
const origin = url.origin;
26+
const { name, artifact_hash, artifact_size_in_bytes }: TypegraphArtifact =
27+
await request
28+
.json();
29+
const uploadUrlMeta: UploadUrlMeta = {
30+
artifactName: name,
31+
artifactHash: artifact_hash,
32+
artifactSizeInBytes: artifact_size_in_bytes,
33+
urlUsed: false,
34+
};
35+
36+
let uploadUrl = createUploadPath(origin, tgName);
37+
38+
const expiresIn = 5 * 60; // 5 minutes
39+
const payload = {
40+
"expiresIn": expiresIn,
41+
};
42+
const token = await signJWT(payload, expiresIn);
43+
uploadUrl = `${uploadUrl}?token=${token}`;
44+
45+
urlCache.set(uploadUrl, uploadUrlMeta);
46+
47+
return new Response(JSON.stringify({ uploadUrl: uploadUrl }));
48+
}
49+
50+
export async function handleArtifactUpload(
51+
request: Request,
52+
tgName: string,
53+
urlCache: Map<string, UploadUrlMeta>,
54+
) {
55+
const url = new URL(request.url);
56+
if (request.method !== "PUT") {
57+
throw new Error(
58+
`${url.pathname} does not support ${request.method} method`,
59+
);
60+
}
61+
62+
const uploadMeta = urlCache.get(url.toString());
63+
64+
if (!uploadMeta) {
65+
throw new Error(`Endpoint ${url.toString()} does not exist`);
66+
}
67+
68+
const token = url.searchParams.get("token");
69+
try {
70+
const _ = await verifyJWT(token!);
71+
} catch (e) {
72+
throw new Error("Invalid token: " + e.toString());
73+
}
74+
75+
const { artifactName, artifactHash, artifactSizeInBytes, urlUsed }:
76+
UploadUrlMeta = uploadMeta!;
77+
78+
if (urlUsed) {
79+
throw new Error(`Endpoint ${url.toString()} is disabled`);
80+
}
81+
82+
const reader = request.body?.getReader()!;
83+
84+
let artifactData = new Uint8Array();
85+
let bytesRead = 0;
86+
while (true) {
87+
const { done, value } = await reader.read();
88+
if (done) {
89+
break;
90+
}
91+
if (value) {
92+
bytesRead += value.length;
93+
const temp = new Uint8Array(artifactData.length + value.length);
94+
temp.set(artifactData);
95+
temp.set(value, artifactData.length);
96+
artifactData = temp;
97+
}
98+
}
99+
100+
if (bytesRead !== artifactSizeInBytes) {
101+
throw new Error(
102+
`File size does not match ${bytesRead}, ${JSON.stringify(uploadMeta)}`,
103+
);
104+
}
105+
106+
// adjust relative to the root path
107+
const artifactStorageDir =
108+
`${config.tmp_dir}/metatype_artifacts/${tgName}/artifacts`;
109+
await Deno.mkdir(artifactStorageDir, { recursive: true });
110+
const artifactPath = `${artifactStorageDir}/${artifactName}.${artifactHash}`;
111+
await Deno.writeFile(artifactPath, artifactData);
112+
113+
// mark as the url used once the request completes.
114+
uploadMeta.urlUsed = true;
115+
urlCache.set(url.toString(), uploadMeta);
116+
117+
return new Response(JSON.stringify({
118+
"success": true,
119+
}));
120+
}

typegate/src/typegate/mod.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import { MigrationFailure } from "../runtimes/prisma/hooks/run_migrations.ts";
3535
import introspectionJson from "../typegraphs/introspection.json" with {
3636
type: "json",
3737
};
38+
import {
39+
handleArtifactUpload,
40+
handleUploadUrl,
41+
} from "../services/artifact_upload_service.ts";
3842

3943
const INTROSPECTION_JSON_STR = JSON.stringify(introspectionJson);
4044

@@ -58,8 +62,16 @@ export type PushResult = {
5862
response: PushResponse;
5963
};
6064

65+
export interface UploadUrlMeta {
66+
artifactName: string;
67+
artifactHash: string;
68+
artifactSizeInBytes: number;
69+
urlUsed: boolean;
70+
}
71+
6172
export class Typegate {
6273
#onPushHooks: PushHandler[] = [];
74+
artifactUploadUrlCache: Map<string, UploadUrlMeta> = new Map();
6375

6476
constructor(
6577
public readonly register: Register,
@@ -113,6 +125,24 @@ export class Typegate {
113125
}
114126

115127
const [engineName, serviceName] = parsePath(url.pathname);
128+
129+
// artifact upload handlers
130+
if (serviceName === "get-upload-url") {
131+
return handleUploadUrl(
132+
request,
133+
engineName,
134+
this.artifactUploadUrlCache,
135+
);
136+
}
137+
138+
if (serviceName === "upload-artifacts") {
139+
return handleArtifactUpload(
140+
request,
141+
engineName,
142+
this.artifactUploadUrlCache,
143+
);
144+
}
145+
116146
if (!engineName || ignoreList.has(engineName)) {
117147
return notFound();
118148
}

typegate/src/typegraph/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ export interface TypeMeta {
507507
rate?: Rate | null;
508508
version: string;
509509
random_seed: number | undefined;
510+
ref_artifacts: Map<string, string>;
510511
}
511512
export interface Queries {
512513
dynamic: boolean;

typegate/src/typegraphs/introspection.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@
597597
},
598598
"auths": [],
599599
"rate": null,
600+
"ref_artifacts": {},
600601
"version": "0.0.3",
601602
"random_seed": null
602603
}

0 commit comments

Comments
 (0)