From ff8a7310d5fd4135124bc3e8b21408a5737e2e0b Mon Sep 17 00:00:00 2001 From: Tiziano Santoro Date: Wed, 29 Apr 2020 18:46:49 +0100 Subject: [PATCH] Move node configuration to runtime The ApplicationConfiguration object is now more of a directory of executable wasm modules, and an initial Node configuration. Subsequent Nodes are created at runtime by passing the serialized NodeConfiguration protobuf message to `node_create` directly. This allows removing an extra indirection from application to the config of individual nodes, in favour of inlining configs in the Wasm code of the main node itself. Ref #688 --- Cargo.lock | 3 + docs/abi.md | 23 +- examples/abitest/abitest_common/src/lib.rs | 3 - examples/abitest/config/BUILD | 4 +- examples/abitest/config/config.textproto | 57 +---- examples/abitest/module_0/rust/src/lib.rs | 200 +++++++++--------- examples/abitest/module_1/rust/src/lib.rs | 5 +- examples/abitest/tests/src/tests.rs | 13 +- examples/aggregator/config/config.textproto | 23 +- examples/aggregator/module/rust/Cargo.toml | 1 + examples/aggregator/module/rust/src/lib.rs | 4 +- examples/chat/config/config.textproto | 17 +- examples/chat/module/rust/Cargo.toml | 1 + examples/chat/module/rust/src/lib.rs | 3 +- examples/hello_world/config/BUILD | 2 +- examples/hello_world/config/config.textproto | 17 +- .../config/config_translator.textproto | 19 -- examples/hello_world/module/rust/src/lib.rs | 4 +- .../machine_learning/config/config.textproto | 17 +- .../config/config.textproto | 17 +- .../running_average/config/config.textproto | 17 +- examples/rustfmt/config/config.textproto | 17 +- examples/tensorflow/config/config.textproto | 17 +- examples/translator/config/config.textproto | 17 +- oak/common/app_config.cc | 61 +----- oak/common/app_config_serializer.cc | 48 ++--- oak/proto/application.proto | 41 ++-- oak/server/rust/oak_abi/build.rs | 1 + oak/server/rust/oak_abi/src/lib.rs | 16 +- oak/server/rust/oak_abi/src/proto/mod.rs | 4 + .../src/proto/oak.application.rs | 45 ++-- oak/server/rust/oak_glue/src/lib.rs | 11 +- oak/server/rust/oak_loader/Cargo.toml | 1 + oak/server/rust/oak_loader/src/main.rs | 5 +- oak/server/rust/oak_runtime/build.rs | 24 --- oak/server/rust/oak_runtime/src/config.rs | 107 ++-------- oak/server/rust/oak_runtime/src/lib.rs | 2 - .../rust/oak_runtime/src/node/grpc_server.rs | 55 ++--- .../rust/oak_runtime/src/node/logger.rs | 31 ++- oak/server/rust/oak_runtime/src/node/mod.rs | 129 +++++------ .../rust/oak_runtime/src/node/wasm/mod.rs | 150 ++++++------- .../rust/oak_runtime/src/node/wasm/tests.rs | 35 +-- oak/server/rust/oak_runtime/src/proto/mod.rs | 21 -- .../rust/oak_runtime/src/runtime/mod.rs | 68 +++--- .../rust/oak_runtime/src/runtime/tests.rs | 47 ++-- .../rust/oak_runtime/tests/config_tests.rs | 115 ---------- .../oak_runtime/tests/integration_test.rs | 28 ++- sdk/rust/oak/src/grpc/client.rs | 15 +- sdk/rust/oak/src/grpc/mod.rs | 25 ++- sdk/rust/oak/src/lib.rs | 29 +-- sdk/rust/oak/src/logger/mod.rs | 10 +- sdk/rust/oak/src/node_config.rs | 57 +++++ sdk/rust/oak/src/roughtime/mod.rs | 24 +-- sdk/rust/oak/src/storage/mod.rs | 24 +-- sdk/rust/oak/src/stubs.rs | 8 +- sdk/rust/oak_tests/src/lib.rs | 26 ++- 56 files changed, 698 insertions(+), 1066 deletions(-) delete mode 100644 examples/hello_world/config/config_translator.textproto rename oak/server/rust/{oak_runtime => oak_abi}/src/proto/oak.application.rs (80%) delete mode 100644 oak/server/rust/oak_runtime/build.rs delete mode 100644 oak/server/rust/oak_runtime/src/proto/mod.rs delete mode 100644 oak/server/rust/oak_runtime/tests/config_tests.rs create mode 100644 sdk/rust/oak/src/node_config.rs diff --git a/Cargo.lock b/Cargo.lock index 84bf1ff4b7d..d8bf0b07d90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,6 +69,7 @@ dependencies = [ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "oak 0.1.0", + "oak_abi 0.1.0", "oak_runtime 0.1.0", "oak_tests 0.1.0", "oak_utils 0.1.0", @@ -368,6 +369,7 @@ dependencies = [ "bincode 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "oak 0.1.0", + "oak_abi 0.1.0", "oak_utils 0.1.0", "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1254,6 +1256,7 @@ version = "0.1.0" dependencies = [ "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "oak_abi 0.1.0", "oak_runtime 0.1.0", "prost 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "structopt 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/docs/abi.md b/docs/abi.md index f7b3dfe0cc0..40a74e92fa0 100644 --- a/docs/abi.md +++ b/docs/abi.md @@ -176,23 +176,22 @@ return the channel handles for its read and write halves. ### node_create -`node_create: (usize, usize, usize, usize, usize, usize, u64) -> u32` creates a -new Node running the Node configuration identified by args 0 and 1, using the -entrypoint specified by args 2 and 3, passing in an initial handle to the read -half of a channel identified by arg 4. The entrypoint name is ignored when -creating non-WebAssembly Nodes. +`node_create: (usize, usize, usize, usize, u64) -> u32` creates a new Node +running the Node configuration identified by args 0 and 1, passing in an initial +handle to the read half of a channel identified by arg 4. + +The Node configuration is a serialized +[`NodeConfiguration`](/oak/proto/application.proto) protobuf message. If creating the specified node would violate [information flow control](/docs/concepts.md#labels), returns `PERMISSION_DENIED`. -- arg 0: Source buffer holding node configuration name -- arg 1: Node configuration name size in bytes -- arg 2: Source buffer holding entrypoint name -- arg 3: Entrypoint name size in bytes -- arg 4: Source buffer holding label -- arg 5: Label size in bytes -- arg 6: Handle to channel +- arg 0: Source buffer holding serialized NodeConfiguration +- arg 1: Serialized NodeConfiguration size in bytes +- arg 2: Source buffer holding label +- arg 3: Label size in bytes +- arg 4: Handle to channel - return 0: Status of operation ### random_get diff --git a/examples/abitest/abitest_common/src/lib.rs b/examples/abitest/abitest_common/src/lib.rs index bd12668a17f..83cf3c2ca69 100644 --- a/examples/abitest/abitest_common/src/lib.rs +++ b/examples/abitest/abitest_common/src/lib.rs @@ -30,6 +30,3 @@ impl oak::io::Encodable for InternalMessage { Ok(oak::io::Message { bytes, handles }) } } - -// Expected name for log node config. -pub const LOG_CONFIG_NAME: &str = "logging-config"; diff --git a/examples/abitest/config/BUILD b/examples/abitest/config/BUILD index f3156ee709b..35745c9e362 100644 --- a/examples/abitest/config/BUILD +++ b/examples/abitest/config/BUILD @@ -25,8 +25,8 @@ exports_files(srcs = glob(["*.textproto"])) serialized_config( name = "config", modules = { - "frontend-config": "//:target/wasm32-unknown-unknown/release/abitest_0_frontend.wasm", - "backend-config": "//:target/wasm32-unknown-unknown/release/abitest_1_backend.wasm", + "frontend_module": "//:target/wasm32-unknown-unknown/release/abitest_0_frontend.wasm", + "backend_module": "//:target/wasm32-unknown-unknown/release/abitest_1_backend.wasm", }, textproto = ":config.textproto", ) diff --git a/examples/abitest/config/config.textproto b/examples/abitest/config/config.textproto index 8e1ecdb9d90..23e42df76fd 100644 --- a/examples/abitest/config/config.textproto +++ b/examples/abitest/config/config.textproto @@ -1,53 +1,8 @@ -node_configs { - name: "frontend-config" - wasm_config { - module_bytes: "" - } -} -node_configs { - name: "backend-config" - wasm_config { - module_bytes: "" - } -} -node_configs { - name: "logging-config" - log_config {} -} -node_configs { - name: "storage" - storage_config { - address: "localhost:7867" - } -} -node_configs { - name: "absent-storage" - storage_config { - address: "test.invalid:9999" - } -} -node_configs { - name: "grpc-client" - grpc_client_config { - address: "localhost:7878" - } -} -node_configs { - name: "absent-grpc-client" - grpc_client_config { - address: "test.invalid:9999" - } -} -node_configs { - name: "roughtime-client" - roughtime_client_config {} -} -node_configs { - name: "roughtime-misconfig" - roughtime_client_config { - min_overlapping_intervals: 99 +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "frontend_module" + wasm_entrypoint_name: "frontend_oak_main" } } -grpc_port: 8080 -initial_node_config_name: "frontend-config" -initial_entrypoint_name: "frontend_oak_main" diff --git a/examples/abitest/module_0/rust/src/lib.rs b/examples/abitest/module_0/rust/src/lib.rs index 0ca7530a8db..f22077336b0 100644 --- a/examples/abitest/module_0/rust/src/lib.rs +++ b/examples/abitest/module_0/rust/src/lib.rs @@ -18,12 +18,17 @@ pub mod proto { include!(concat!(env!("OUT_DIR"), "/oak.examples.abitest.rs")); } -use abitest_common::{InternalMessage, LOG_CONFIG_NAME}; +use abitest_common::InternalMessage; use byteorder::WriteBytesExt; use expect::{expect, expect_eq, expect_matches}; use log::{debug, error, info, trace, warn}; use oak::{grpc, ChannelReadStatus, OakError, OakStatus}; -use oak_abi::label::Label; +use oak_abi::{ + label::Label, + proto::oak::application::{ + NodeConfiguration, RoughtimeClientConfiguration, StorageProxyConfiguration, + }, +}; use prost::Message; use proto::{ AbiTestRequest, AbiTestResponse, GrpcTestRequest, GrpcTestResponse, OakAbiTestService, @@ -34,8 +39,8 @@ use std::{collections::HashMap, convert::TryInto}; const BACKEND_COUNT: usize = 3; -const FRONTEND_CONFIG_NAME: &str = "frontend-config"; -const BACKEND_CONFIG_NAME: &str = "backend-config"; +const FRONTEND_MODULE_NAME: &str = "frontend_module"; +const BACKEND_MODULE_NAME: &str = "backend_module"; const BACKEND_ENTRYPOINT_NAME: &str = "backend_oak_main"; const STORAGE_NAME_PREFIX: &str = "abitest"; @@ -64,13 +69,15 @@ impl FrontendNode { // Second, start an ephemeral Node which also loses channels. let (wh, rh) = oak::channel_create().unwrap(); - oak::node_create(FRONTEND_CONFIG_NAME, "channel_loser", rh) - .expect("failed to create channel_loser ephemeral Node"); + oak::node_create( + &oak::node_config::wasm(FRONTEND_MODULE_NAME, "channel_loser"), + rh, + ) + .expect("failed to create channel_loser ephemeral Node"); oak::channel_close(wh.handle).unwrap(); oak::channel_close(rh.handle).unwrap(); } - oak::logger::init(log::Level::Debug, LOG_CONFIG_NAME) - .expect("could not initialize logging node"); + oak::logger::init(log::Level::Debug).expect("could not initialize logging node"); // Create backend node instances. let mut backend_out = Vec::with_capacity(BACKEND_COUNT); @@ -78,8 +85,11 @@ impl FrontendNode { for i in 0..BACKEND_COUNT { let (write_handle, read_handle) = oak::channel_create().expect("could not create channel"); - oak::node_create(BACKEND_CONFIG_NAME, BACKEND_ENTRYPOINT_NAME, read_handle) - .expect("could not create node"); + oak::node_create( + &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME), + read_handle, + ) + .expect("could not create node"); oak::channel_close(read_handle.handle).expect("could not close channel"); backend_out.push(write_handle); @@ -102,15 +112,30 @@ impl FrontendNode { FrontendNode { backend_out, backend_in, - storage: oak::storage::Storage::default(), - absent_storage: oak::storage::Storage::new("absent-storage"), + storage: oak::storage::Storage::new(&StorageProxyConfiguration { + address: "localhost:7867".to_string(), + }), + absent_storage: oak::storage::Storage::new(&StorageProxyConfiguration { + address: "test.invalid:9999".to_string(), + }), storage_name: storage_name.as_bytes().to_vec(), - grpc_service: oak::grpc::client::Client::new("grpc-client", "ignored") - .map(OakAbiTestServiceClient), - absent_grpc_service: oak::grpc::client::Client::new("absent-grpc-client", "ignored") - .map(OakAbiTestServiceClient), - roughtime: oak::roughtime::Roughtime::new("roughtime-client"), - misconfigured_roughtime: oak::roughtime::Roughtime::new("roughtime-misconfig"), + grpc_service: oak::grpc::client::Client::new(&oak::node_config::grpc_client( + "localhost:7878", + )) + .map(OakAbiTestServiceClient), + absent_grpc_service: oak::grpc::client::Client::new(&oak::node_config::grpc_client( + "test.invalid:9999", + )) + .map(OakAbiTestServiceClient), + roughtime: oak::roughtime::Roughtime::new(&RoughtimeClientConfiguration { + ..Default::default() + }), + misconfigured_roughtime: oak::roughtime::Roughtime::new( + &RoughtimeClientConfiguration { + min_overlapping_intervals: 99, + ..Default::default() + }, + ), } } } @@ -1121,127 +1146,104 @@ impl FrontendNode { fn test_node_create_raw(&mut self) -> TestResult { let (_, in_channel, _) = channel_create_raw(); - let valid = "a_string"; - let non_utf8_name: Vec = vec![0xc3, 0x28]; let valid_label_bytes = Label::public_trusted().serialize(); - // This sequence of bytes should not deserialize as a [`oak_abi::proto::policy::Label`] - // protobuf. We make sure here that this continues to be the case by making sure that - // [`Label::deserialize`] fails to parse these bytes. - let invalid_label_bytes = vec![0, 88, 0]; - assert_eq!(None, Label::deserialize(&invalid_label_bytes)); - - unsafe { - expect_eq!( - OakStatus::Ok as u32, - oak_abi::node_create( - BACKEND_CONFIG_NAME.as_ptr(), - BACKEND_CONFIG_NAME.len(), - BACKEND_ENTRYPOINT_NAME.as_ptr(), - BACKEND_ENTRYPOINT_NAME.len(), - valid_label_bytes.as_ptr(), - valid_label_bytes.len(), - in_channel - ) - ); - - expect_eq!( - OakStatus::ErrInvalidArgs as u32, - oak_abi::node_create( - BACKEND_CONFIG_NAME.as_ptr(), - BACKEND_CONFIG_NAME.len(), - BACKEND_ENTRYPOINT_NAME.as_ptr(), - BACKEND_ENTRYPOINT_NAME.len(), - invalid_label_bytes.as_ptr(), - invalid_label_bytes.len(), - in_channel - ) - ); + // This sequence of bytes should not deserialize as a [`oak_abi::proto::policy::Label`] or + // [`oak_abi::proto::oak::application::NodeConfiguration`] protobuf. We make sure + // here that this continues to be the case by making sure that [`Label::decode`] and + // [`NodeConfiguration::decode`] fail to parse these bytes. + let invalid_proto_bytes = vec![0, 88, 0]; + assert_eq!(false, Label::decode(invalid_proto_bytes.as_ref()).is_ok()); + assert_eq!( + false, + NodeConfiguration::decode(invalid_proto_bytes.as_ref()).is_ok() + ); - expect_eq!( - OakStatus::ErrInvalidArgs as u32, + { + let mut config_bytes = Vec::new(); + oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME) + .encode(&mut config_bytes)?; + expect_eq!(OakStatus::Ok as u32, unsafe { oak_abi::node_create( - invalid_raw_offset() as *mut u8, - 1, - valid.as_ptr(), - valid.len(), + config_bytes.as_ptr(), + config_bytes.len(), valid_label_bytes.as_ptr(), valid_label_bytes.len(), - in_channel + in_channel, ) - ); + }); + } - expect_eq!( - OakStatus::ErrInvalidArgs as u32, + { + let mut config_bytes = Vec::new(); + oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME) + .encode(&mut config_bytes)?; + expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe { oak_abi::node_create( - non_utf8_name.as_ptr(), - non_utf8_name.len(), - valid.as_ptr(), - valid.len(), - valid_label_bytes.as_ptr(), - valid_label_bytes.len(), - in_channel + config_bytes.as_ptr(), + config_bytes.len(), + invalid_proto_bytes.as_ptr(), + invalid_proto_bytes.len(), + in_channel, ) - ); + }); + } - expect_eq!( - OakStatus::ErrInvalidArgs as u32, + { + expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe { oak_abi::node_create( - valid.as_ptr(), - valid.len(), invalid_raw_offset() as *mut u8, 1, valid_label_bytes.as_ptr(), valid_label_bytes.len(), - in_channel + in_channel, ) - ); + }); + } - expect_eq!( - OakStatus::ErrInvalidArgs as u32, + { + expect_eq!(OakStatus::ErrInvalidArgs as u32, unsafe { oak_abi::node_create( - valid.as_ptr(), - valid.len(), - non_utf8_name.as_ptr(), - non_utf8_name.len(), + invalid_proto_bytes.as_ptr(), + invalid_proto_bytes.len(), valid_label_bytes.as_ptr(), valid_label_bytes.len(), - in_channel + in_channel, ) - ); + }); } + Ok(()) } fn test_node_create(&mut self) -> TestResult { expect_eq!( Err(OakStatus::ErrInvalidArgs), oak::node_create( - "no-such-config", - BACKEND_ENTRYPOINT_NAME, + &oak::node_config::wasm("no_such_module", BACKEND_ENTRYPOINT_NAME), self.backend_in[0] ) ); expect_eq!( Err(OakStatus::ErrInvalidArgs), oak::node_create( - BACKEND_CONFIG_NAME, - "no-such-entrypoint", + &oak::node_config::wasm(BACKEND_MODULE_NAME, "no_such_entrypoint"), self.backend_in[0] ) ); expect_eq!( Err(OakStatus::ErrInvalidArgs), oak::node_create( - BACKEND_CONFIG_NAME, - "backend_fake_main", // exists but wrong signature + &oak::node_config::wasm( + BACKEND_MODULE_NAME, + "backend_fake_main" /* exists but wrong signature */ + ), self.backend_in[0] ) ); expect_eq!( Err(OakStatus::ErrBadHandle), oak::node_create( - BACKEND_CONFIG_NAME, - BACKEND_ENTRYPOINT_NAME, + &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME), oak::ReadHandle { handle: oak::Handle::from_raw(oak_abi::INVALID_HANDLE) } @@ -1250,11 +1252,17 @@ impl FrontendNode { let (out_handle, in_handle) = oak::channel_create().unwrap(); expect_eq!( Ok(()), - oak::node_create(BACKEND_CONFIG_NAME, BACKEND_ENTRYPOINT_NAME, in_handle) + oak::node_create( + &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME), + in_handle + ) ); expect_eq!( Ok(()), - oak::node_create(BACKEND_CONFIG_NAME, BACKEND_ENTRYPOINT_NAME, in_handle) + oak::node_create( + &oak::node_config::wasm(BACKEND_MODULE_NAME, BACKEND_ENTRYPOINT_NAME), + in_handle + ) ); expect_eq!(Ok(()), oak::channel_close(in_handle.handle)); @@ -1353,7 +1361,7 @@ impl FrontendNode { // Include some handles which will be ignored. let (logging_handle, read_handle) = oak::channel_create().expect("could not create channel"); - oak::node_create(LOG_CONFIG_NAME, "oak_main", read_handle).expect("could not create node"); + oak::node_create(&oak::node_config::log(), read_handle).expect("could not create node"); oak::channel_close(read_handle.handle).expect("could not close channel"); expect!(logging_handle.handle.is_valid()); @@ -1378,7 +1386,7 @@ impl FrontendNode { oak::channel_write( logging_handle, - &bytes[..], + bytes.as_ref(), &[in_handle.handle, out_handle.handle], ) .expect("could not write to channel"); diff --git a/examples/abitest/module_1/rust/src/lib.rs b/examples/abitest/module_1/rust/src/lib.rs index ae28d9b3310..2d364644bea 100644 --- a/examples/abitest/module_1/rust/src/lib.rs +++ b/examples/abitest/module_1/rust/src/lib.rs @@ -14,7 +14,7 @@ // limitations under the License. // -use abitest_common::{InternalMessage, LOG_CONFIG_NAME}; +use abitest_common::InternalMessage; use log::{error, info}; use std::collections::HashSet; @@ -42,8 +42,7 @@ pub extern "C" fn backend_oak_main(handle: u64) { } fn inner_main(in_handle: u64) -> Result<(), oak::OakStatus> { - oak::logger::init(log::Level::Debug, LOG_CONFIG_NAME) - .expect("Couldn't initialize logging node!"); + oak::logger::init(log::Level::Debug).expect("Couldn't initialize logging node!"); let in_channel = oak::ReadHandle { handle: oak::Handle::from_raw(in_handle), }; diff --git a/examples/abitest/tests/src/tests.rs b/examples/abitest/tests/src/tests.rs index 91f61beb0d2..e5f4e370c7e 100644 --- a/examples/abitest/tests/src/tests.rs +++ b/examples/abitest/tests/src/tests.rs @@ -23,9 +23,8 @@ use std::collections::HashMap; // Constants for Node config names that should match those in the textproto // config held in ../../client/config.h. -const FRONTEND_CONFIG_NAME: &str = "frontend-config"; -const BACKEND_CONFIG_NAME: &str = "backend-config"; -const LOG_CONFIG_NAME: &str = "logging-config"; +const FRONTEND_MODULE_NAME: &str = "frontend_module"; +const BACKEND_MODULE_NAME: &str = "backend_module"; const FRONTEND_ENTRYPOINT_NAME: &str = "frontend_oak_main"; const FRONTEND_MANIFEST: &str = "../module_0/rust/Cargo.toml"; @@ -36,8 +35,8 @@ const BACKEND_WASM_NAME: &str = "abitest_1_backend.wasm"; fn build_wasm() -> std::io::Result>> { Ok(hashmap![ - FRONTEND_CONFIG_NAME.to_owned() => oak_tests::compile_rust_wasm(FRONTEND_MANIFEST, FRONTEND_WASM_NAME)?, - BACKEND_CONFIG_NAME.to_owned() => oak_tests::compile_rust_wasm(BACKEND_MANIFEST, BACKEND_WASM_NAME)?, + FRONTEND_MODULE_NAME.to_owned() => oak_tests::compile_rust_wasm(FRONTEND_MANIFEST, FRONTEND_WASM_NAME)?, + BACKEND_MODULE_NAME.to_owned() => oak_tests::compile_rust_wasm(BACKEND_MANIFEST, BACKEND_WASM_NAME)?, ]) } @@ -47,9 +46,7 @@ fn test_abi() { let configuration = oak_runtime::application_configuration( build_wasm().expect("failed to build wasm modules"), - LOG_CONFIG_NAME, - FRONTEND_CONFIG_NAME, - FRONTEND_ENTRYPOINT_NAME, + &oak::node_config::wasm(FRONTEND_MODULE_NAME, FRONTEND_ENTRYPOINT_NAME), ); let (runtime, entry_channel) = oak_runtime::configure_and_run(configuration) diff --git a/examples/aggregator/config/config.textproto b/examples/aggregator/config/config.textproto index bdd86ee8af6..6da9879a89f 100644 --- a/examples/aggregator/config/config.textproto +++ b/examples/aggregator/config/config.textproto @@ -1,19 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" - } -} -node_configs { - name: "grpc-client" - grpc_client_config { - address: "127.0.0.1:8888" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/aggregator/module/rust/Cargo.toml b/examples/aggregator/module/rust/Cargo.toml index 0d02bec8faf..d1a8b835e44 100644 --- a/examples/aggregator/module/rust/Cargo.toml +++ b/examples/aggregator/module/rust/Cargo.toml @@ -12,6 +12,7 @@ aggregator_common = { path = "../../common" } itertools = "*" log = "*" oak = "=0.1.0" +oak_abi = "=0.1.0" prost = "*" [dev-dependencies] diff --git a/examples/aggregator/module/rust/src/lib.rs b/examples/aggregator/module/rust/src/lib.rs index fb0d52b53e5..a8f32d12b9a 100644 --- a/examples/aggregator/module/rust/src/lib.rs +++ b/examples/aggregator/module/rust/src/lib.rs @@ -92,7 +92,9 @@ impl AggregatorNode { bucket, svec ); - match oak::grpc::client::Client::new("grpc-client", "").map(AggregatorClient) { + match oak::grpc::client::Client::new(&oak::node_config::grpc_client("127.0.0.1:8888")) + .map(AggregatorClient) + { Some(grpc_client) => { let res = grpc_client.submit_sample(Sample { bucket, diff --git a/examples/chat/config/config.textproto b/examples/chat/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/chat/config/config.textproto +++ b/examples/chat/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/chat/module/rust/Cargo.toml b/examples/chat/module/rust/Cargo.toml index 07e3277de77..9c083f05ae0 100644 --- a/examples/chat/module/rust/Cargo.toml +++ b/examples/chat/module/rust/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib"] bincode = "*" log = "*" oak = "=0.1.0" +oak_abi = "=0.1.0" prost = "*" rand_core = "*" rand = "*" diff --git a/examples/chat/module/rust/src/lib.rs b/examples/chat/module/rust/src/lib.rs index 6a4faf4d5e3..cd44d9a2222 100644 --- a/examples/chat/module/rust/src/lib.rs +++ b/examples/chat/module/rust/src/lib.rs @@ -51,7 +51,8 @@ struct Room { impl Room { fn new(admin_token: AdminToken) -> Self { let (wh, rh) = oak::channel_create().unwrap(); - oak::node_create("app", "backend_oak_main", rh).expect("could not create node"); + oak::node_create(&oak::node_config::wasm("app", "backend_oak_main"), rh) + .expect("could not create node"); oak::channel_close(rh.handle).expect("could not close channel"); Room { sender: oak::io::Sender::new(wh), diff --git a/examples/hello_world/config/BUILD b/examples/hello_world/config/BUILD index 26baf493d13..15dad437575 100644 --- a/examples/hello_world/config/BUILD +++ b/examples/hello_world/config/BUILD @@ -45,5 +45,5 @@ serialized_config( "app": "//:target/wasm32-unknown-unknown/release/hello_world.wasm", "translator": "//:target/wasm32-unknown-unknown/release/translator.wasm", }, - textproto = ":config_translator.textproto", + textproto = ":config.textproto", ) diff --git a/examples/hello_world/config/config.textproto b/examples/hello_world/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/hello_world/config/config.textproto +++ b/examples/hello_world/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/hello_world/config/config_translator.textproto b/examples/hello_world/config/config_translator.textproto deleted file mode 100644 index 66b2601c500..00000000000 --- a/examples/hello_world/config/config_translator.textproto +++ /dev/null @@ -1,19 +0,0 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" - } -} -node_configs { - name: "translator" - wasm_config { - module_bytes: "" - } -} -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/hello_world/module/rust/src/lib.rs b/examples/hello_world/module/rust/src/lib.rs index ba0c18af978..21c49874238 100644 --- a/examples/hello_world/module/rust/src/lib.rs +++ b/examples/hello_world/module/rust/src/lib.rs @@ -21,13 +21,13 @@ mod proto { mod tests; use log::info; -use oak::grpc; +use oak::{self, grpc}; use proto::{HelloRequest, HelloResponse, HelloWorld, HelloWorldDispatcher}; oak::entrypoint!(oak_main => { oak::logger::init_default(); let node = Node { - translator: grpc::client::Client::new("translator", "oak_main") + translator: grpc::client::Client::new(&oak::node_config::wasm("translator", "oak_main")) .map(translator_common::TranslatorClient), }; HelloWorldDispatcher::new(node) diff --git a/examples/machine_learning/config/config.textproto b/examples/machine_learning/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/machine_learning/config/config.textproto +++ b/examples/machine_learning/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/private_set_intersection/config/config.textproto b/examples/private_set_intersection/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/private_set_intersection/config/config.textproto +++ b/examples/private_set_intersection/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/running_average/config/config.textproto b/examples/running_average/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/running_average/config/config.textproto +++ b/examples/running_average/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/rustfmt/config/config.textproto b/examples/rustfmt/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/rustfmt/config/config.textproto +++ b/examples/rustfmt/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/tensorflow/config/config.textproto b/examples/tensorflow/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/tensorflow/config/config.textproto +++ b/examples/tensorflow/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/examples/translator/config/config.textproto b/examples/translator/config/config.textproto index e5a610a8a78..6da9879a89f 100644 --- a/examples/translator/config/config.textproto +++ b/examples/translator/config/config.textproto @@ -1,13 +1,8 @@ -node_configs { - name: "app" - wasm_config { - module_bytes: "" +grpc_port: 8080 +initial_node_configuration: { + name: "main" + wasm_config: { + wasm_module_name: "app" + wasm_entrypoint_name: "oak_main" } } -node_configs { - name: "log" - log_config {} -} -grpc_port: 8080 -initial_node_config_name: "app" -initial_entrypoint_name: "oak_main" diff --git a/oak/common/app_config.cc b/oak/common/app_config.cc index 2c00915380d..9404b951b07 100644 --- a/oak/common/app_config.cc +++ b/oak/common/app_config.cc @@ -34,23 +34,21 @@ constexpr int16_t kDefaultGrpcPort = 8080; // Conventional names for the configuration of Nodes. constexpr char kAppConfigName[] = "app"; -constexpr char kAppEntrypointName[] = "oak_main"; -constexpr char kLogConfigName[] = "log"; -constexpr char kStorageConfigName[] = "storage"; -constexpr char kGrpcClientConfigName[] = "grpc-client"; +constexpr char kAppWasmModuleName[] = "oak_module"; +constexpr char kAppWasmEntrypointName[] = "oak_main"; } // namespace std::unique_ptr DefaultConfig(const std::string& module_bytes) { auto config = absl::make_unique(); config->set_grpc_port(kDefaultGrpcPort); + (*config->mutable_wasm_modules())[kAppWasmModuleName] = module_bytes; - config->set_initial_node_config_name(kAppConfigName); - NodeConfiguration* node_config = config->add_node_configs(); + auto node_config = config->mutable_initial_node_configuration(); node_config->set_name(kAppConfigName); - application::WebAssemblyConfiguration* code = node_config->mutable_wasm_config(); - code->set_module_bytes(module_bytes); - config->set_initial_entrypoint_name(kAppEntrypointName); + application::WebAssemblyConfiguration* wasm_config = node_config->mutable_wasm_config(); + wasm_config->set_wasm_module_name(kAppWasmModuleName); + wasm_config->set_wasm_entrypoint_name(kAppWasmEntrypointName); return config; } @@ -69,26 +67,6 @@ void WriteConfigToFile(const ApplicationConfiguration* config, const std::string utils::write_file(data, filename); } -void AddLoggingToConfig(ApplicationConfiguration* config) { - NodeConfiguration* node_config = config->add_node_configs(); - node_config->set_name(kLogConfigName); - node_config->mutable_log_config(); -} - -void AddStorageToConfig(ApplicationConfiguration* config, const std::string& storage_address) { - NodeConfiguration* node_config = config->add_node_configs(); - node_config->set_name(kStorageConfigName); - application::StorageProxyConfiguration* storage = node_config->mutable_storage_config(); - storage->set_address(storage_address); -} - -void AddGrpcClientToConfig(ApplicationConfiguration* config, const std::string& grpc_address) { - NodeConfiguration* node_config = config->add_node_configs(); - node_config->set_name(kGrpcClientConfigName); - application::GrpcClientConfiguration* grpc_config = node_config->mutable_grpc_client_config(); - grpc_config->set_address(grpc_address); -} - void SetGrpcPortInConfig(ApplicationConfiguration* config, const int16_t grpc_port) { config->set_grpc_port(grpc_port); } @@ -100,30 +78,11 @@ bool ValidApplicationConfig(const ApplicationConfiguration& config) { return false; } - // Check name uniqueness for NodeConfiguration. - std::set config_names; - std::set wasm_names; - for (const auto& node_config : config.node_configs()) { - if (config_names.count(node_config.name()) > 0) { - OAK_LOG(ERROR) << "duplicate node config name " << node_config.name(); - return false; - } - config_names.insert(node_config.name()); - if (node_config.has_wasm_config()) { - wasm_names.insert(node_config.name()); - } - } - - // Check name for the config of the initial node is present and is a Web - // Assembly variant. - if (wasm_names.count(config.initial_node_config_name()) == 0) { - OAK_LOG(ERROR) << "config of the initial node is not present in Wasm"; - return false; - } - if (config.initial_entrypoint_name().empty()) { - OAK_LOG(ERROR) << "missing entrypoint name"; + if (!config.has_initial_node_configuration()) { + OAK_LOG(ERROR) << "Missing initial node configuration"; return false; } + return true; } diff --git a/oak/common/app_config_serializer.cc b/oak/common/app_config_serializer.cc index 8537ad22c3d..95e73e4b41e 100644 --- a/oak/common/app_config_serializer.cc +++ b/oak/common/app_config_serializer.cc @@ -29,10 +29,10 @@ ABSL_FLAG( std::string, textproto, "", - "Textproto file with an application configuration, where the `module_bytes` value is empty, " + "Textproto file with an application configuration, where the `wasm_modules` field is empty, " "(it will be overwritten by module bytes after serialization)"); ABSL_FLAG(std::vector, modules, std::vector{}, - "A comma-separated list of entries `module:path` with files containing compiled " + "A comma-separated list of entries `module_name:path` with files containing compiled " "WebAssembly modules to insert into the generated configuration"); ABSL_FLAG(std::string, output_file, "", "File to write an application configuration to"); @@ -41,51 +41,49 @@ int main(int argc, char* argv[]) { std::string textproto = absl::GetFlag(FLAGS_textproto); if (textproto.empty()) { OAK_LOG(FATAL) << "Textproto file is not specified"; + return 1; } std::vector modules = absl::GetFlag(FLAGS_modules); if (modules.empty()) { OAK_LOG(FATAL) << "Wasm modules are not specified"; + return 1; } std::string output_file = absl::GetFlag(FLAGS_output_file); if (output_file.empty()) { OAK_LOG(FATAL) << "Output file is not specified"; + return 1; } - // Parse module names. + // Load application configuration. + auto config = absl::make_unique(); + std::string textproto_string = oak::utils::read_file(textproto); + if (!google::protobuf::TextFormat::MergeFromString(textproto_string, config.get())) { + OAK_LOG(FATAL) << "Error parsing proto"; + return 1; + } + + // Parse module names and add Wasm module bytes to the application configuration. std::map module_map; for (const std::string& module : absl::GetFlag(FLAGS_modules)) { std::vector module_info = absl::StrSplit(module, ':'); if (module_info.size() != 2) { OAK_LOG(FATAL) << "Incorrect module specification: " << module; + return 1; } - module_map.emplace(module_info.front(), module_info.back()); - } - - // Load application configuration. - auto config = absl::make_unique(); - std::string textproto_string = oak::utils::read_file(textproto); - google::protobuf::TextFormat::MergeFromString(textproto_string, config.get()); - - // Add Wasm module bytes to the application configuration. - for (auto& node_config : *config->mutable_node_configs()) { - if (node_config.has_wasm_config()) { - std::string module_name = node_config.name(); - auto it = module_map.find(module_name); - if (it != module_map.end()) { - std::string module_bytes = oak::utils::read_file(it->second); - if (module_bytes.empty()) { - OAK_LOG(FATAL) << "Empty Wasm module:" << module_name; - } - node_config.mutable_wasm_config()->set_module_bytes(module_bytes); - } else { - OAK_LOG(FATAL) << "Module path for " << module_name << " is not specified"; - } + std::string module_name = module_info[0]; + std::string module_path = module_info[1]; + std::string module_bytes = oak::utils::read_file(module_path); + if (module_bytes.empty()) { + OAK_LOG(FATAL) << "Empty Wasm module:" << module_name; + return 1; } + (*config->mutable_wasm_modules())[module_name] = std::move(module_bytes); } // Check application configuration validity. if (!oak::ValidApplicationConfig(*config)) { OAK_LOG(FATAL) << "Application config is not valid"; + return 1; } oak::WriteConfigToFile(config.get(), output_file); diff --git a/oak/proto/application.proto b/oak/proto/application.proto index 6ea9f968465..617714d9061 100644 --- a/oak/proto/application.proto +++ b/oak/proto/application.proto @@ -22,27 +22,36 @@ package oak.application; // // An Oak Application is built from a collection of interconnected Nodes, // each of which is running the code described by an entry in this -// configuration. These Nodes are created dynamically at runtime, with +// configuration. These Nodes are created dynamically at runtime, with // the exception of the specified initial Node (which is created by the // Oak runtime). message ApplicationConfiguration { - // Collection of available Node configurations, indexed by name (which must be - // unique across the collection). Each Node in the application will run under - // a configuration that is identified by an entry in this collection. - repeated NodeConfiguration node_configs = 1; - // Indication of what configuration the initial Node should run. Must identify a - // NodeConfiguration entry that holds a WebAssemblyConfiguration object. - string initial_node_config_name = 2; - // The name of an exported Web Assembly function in the initial Node to - // be used as the Node's main entrypoint. - string initial_entrypoint_name = 3; + // Map from Wasm module names to their bytecode representation. + // + // See https://webassembly.org/docs/binary-encoding/ . + // + // Any developer-authored code running as part of an Oak Application must appear in this map. + // + // Entries in this map may be referenced by instances of WebAssemblyConfiguration + // objects. + map wasm_modules = 1; + + // Indication of what configuration the initial Node should run. + // + // Usually a NodeConfiguration entry that holds a WebAssemblyConfiguration object. + NodeConfiguration initial_node_configuration = 2; + // Port number used by the gRPC pseudo-node; must be >= 1024. int32 grpc_port = 4; } // NodeConfiguration indicates the configuration of a created Node. message NodeConfiguration { + // Display name of the newly created node instance, for debugging purposes. + // + // Does not need to be unique. string name = 1; + oneof config_type { WebAssemblyConfiguration wasm_config = 2; LogConfiguration log_config = 3; @@ -55,11 +64,11 @@ message NodeConfiguration { // WebAssemblyConfiguration describes the configuration of a Web Assembly based Node. message WebAssemblyConfiguration { - // The compiled code of the Oak Node, in WebAssembly binary format. - // See https://webassembly.org/docs/binary-encoding/ . - // TODO(#804): Replace this with just a hash of the bytecode to instantiate, and - // pass the actual bytecode to the Oak Manager in some other way. - bytes module_bytes = 1; + // The name of one of the entries in the `ApplicationConfiguration.wasm_modules` field. + string wasm_module_name = 1; + + // The name of an exported WebAssembly function to invoke as the Node entry point. + string wasm_entrypoint_name = 2; } // LogConfiguration describes the configuration of a logging pseudo-Node (which diff --git a/oak/server/rust/oak_abi/build.rs b/oak/server/rust/oak_abi/build.rs index 63f369563ae..8607076299b 100644 --- a/oak/server/rust/oak_abi/build.rs +++ b/oak/server/rust/oak_abi/build.rs @@ -18,6 +18,7 @@ fn main() { // TODO(#850): get Prost code generation working in Bazel. oak_utils::compile_protos_to( &[ + "../../../../oak/proto/application.proto", "../../../../oak/proto/grpc_encap.proto", "../../../../oak/proto/label.proto", "../../../../oak/proto/log.proto", diff --git a/oak/server/rust/oak_abi/src/lib.rs b/oak/server/rust/oak_abi/src/lib.rs index decc5310c2b..16a52b9faaa 100644 --- a/oak/server/rust/oak_abi/src/lib.rs +++ b/oak/server/rust/oak_abi/src/lib.rs @@ -119,31 +119,25 @@ extern "C" { /// [`OakStatus`]: crate::OakStatus pub fn channel_create(write: *mut u64, read: *mut u64) -> u32; - /// Close a channel. - /// - /// Close the channel identified by `handle`. + /// Closes the channel identified by `handle`. /// /// Returns the status of the operation, as an [`OakStatus`] value. /// /// [`OakStatus`]: crate::OakStatus pub fn channel_close(handle: u64) -> u32; - /// Create a new Node instance running code identified by configuration - /// name and entrypoint; the entrypoint is only used when creating a - /// WebAssembly Node; it is ignored when creating a pseudo-Node. + /// Creates a new Node instance running code identified by a serialized [`NodeConfiguration`]. /// - /// The configuration name is provided in the memory area given by - /// `config_buf` and `config_len`; the entrypoint name is provided in the - /// memory area given by `entrypoint_buf` and `entrypoint_len`. + /// The serialized configuration object is provided in the memory area given by `config_buf` and + /// `config_len`. /// /// Returns the status of the operation, as an [`OakStatus`] value. /// /// [`OakStatus`]: crate::OakStatus + /// [`NodeConfiguration`]: crate::proto::oak::application::NodeConfiguration pub fn node_create( config_buf: *const u8, config_len: usize, - entrypoint_buf: *const u8, - entrypoint_len: usize, label_buf: *const u8, label_len: usize, handle: u64, diff --git a/oak/server/rust/oak_abi/src/proto/mod.rs b/oak/server/rust/oak_abi/src/proto/mod.rs index bb1abb29843..6c6354a6f9e 100644 --- a/oak/server/rust/oak_abi/src/proto/mod.rs +++ b/oak/server/rust/oak_abi/src/proto/mod.rs @@ -23,6 +23,10 @@ pub mod google { pub mod oak { include!("oak_abi.rs"); + pub mod application { + include!("oak.application.rs"); + } + pub mod label { include!("oak.label.rs"); } diff --git a/oak/server/rust/oak_runtime/src/proto/oak.application.rs b/oak/server/rust/oak_abi/src/proto/oak.application.rs similarity index 80% rename from oak/server/rust/oak_runtime/src/proto/oak.application.rs rename to oak/server/rust/oak_abi/src/proto/oak.application.rs index 7d6b4cbe3e2..7df6c4e537a 100644 --- a/oak/server/rust/oak_runtime/src/proto/oak.application.rs +++ b/oak/server/rust/oak_abi/src/proto/oak.application.rs @@ -2,24 +2,26 @@ /// /// An Oak Application is built from a collection of interconnected Nodes, /// each of which is running the code described by an entry in this -/// configuration. These Nodes are created dynamically at runtime, with +/// configuration. These Nodes are created dynamically at runtime, with /// the exception of the specified initial Node (which is created by the /// Oak runtime). #[derive(Clone, PartialEq, ::prost::Message)] pub struct ApplicationConfiguration { - /// Collection of available Node configurations, indexed by name (which must be - /// unique across the collection). Each Node in the application will run under - /// a configuration that is identified by an entry in this collection. - #[prost(message, repeated, tag="1")] - pub node_configs: ::std::vec::Vec, - /// Indication of what configuration the initial Node should run. Must identify a - /// NodeConfiguration entry that holds a WebAssemblyConfiguration object. - #[prost(string, tag="2")] - pub initial_node_config_name: std::string::String, - /// The name of an exported Web Assembly function in the initial Node to - /// be used as the Node's main entrypoint. - #[prost(string, tag="3")] - pub initial_entrypoint_name: std::string::String, + /// Map from Wasm module names to their bytecode representation. + /// + /// See https://webassembly.org/docs/binary-encoding/ . + /// + /// Any developer-authored code running as part of an Oak Application must appear in this map. + /// + /// Entries in this map may be referenced by instances of WebAssemblyConfiguration + /// objects. + #[prost(map="string, bytes", tag="1")] + pub wasm_modules: ::std::collections::HashMap>, + /// Indication of what configuration the initial Node should run. + /// + /// Usually a NodeConfiguration entry that holds a WebAssemblyConfiguration object. + #[prost(message, optional, tag="2")] + pub initial_node_configuration: ::std::option::Option, /// Port number used by the gRPC pseudo-node; must be >= 1024. #[prost(int32, tag="4")] pub grpc_port: i32, @@ -27,6 +29,9 @@ pub struct ApplicationConfiguration { /// NodeConfiguration indicates the configuration of a created Node. #[derive(Clone, PartialEq, ::prost::Message)] pub struct NodeConfiguration { + /// Display name of the newly created node instance, for debugging purposes. + /// + /// Does not need to be unique. #[prost(string, tag="1")] pub name: std::string::String, #[prost(oneof="node_configuration::ConfigType", tags="2, 3, 4, 5, 6, 7")] @@ -52,12 +57,12 @@ pub mod node_configuration { /// WebAssemblyConfiguration describes the configuration of a Web Assembly based Node. #[derive(Clone, PartialEq, ::prost::Message)] pub struct WebAssemblyConfiguration { - /// The compiled code of the Oak Node, in WebAssembly binary format. - /// See https://webassembly.org/docs/binary-encoding/ . - /// TODO(#804): Replace this with just a hash of the bytecode to instantiate, and - /// pass the actual bytecode to the Oak Manager in some other way. - #[prost(bytes, tag="1")] - pub module_bytes: std::vec::Vec, + /// The name of one of the entries in the `ApplicationConfiguration.wasm_modules` field. + #[prost(string, tag="1")] + pub wasm_module_name: std::string::String, + /// The name of an exported WebAssembly function to invoke as the Node entry point. + #[prost(string, tag="2")] + pub wasm_entrypoint_name: std::string::String, } /// LogConfiguration describes the configuration of a logging pseudo-Node (which /// is provided by the Oak Runtime). diff --git a/oak/server/rust/oak_glue/src/lib.rs b/oak/server/rust/oak_glue/src/lib.rs index b75372fb75e..4efc0ac0df5 100644 --- a/oak/server/rust/oak_glue/src/lib.rs +++ b/oak/server/rust/oak_glue/src/lib.rs @@ -17,10 +17,8 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; use lazy_static::lazy_static; use log::{debug, error, info, warn}; -use oak_abi::OakStatus; -use oak_runtime::{ - proto::oak::application::ApplicationConfiguration, runtime::RuntimeProxy, NodeId, -}; +use oak_abi::{proto::oak::application::ApplicationConfiguration, OakStatus}; +use oak_runtime::{runtime::RuntimeProxy, NodeId}; use prost::Message; use std::{ convert::TryInto, @@ -101,10 +99,7 @@ pub unsafe extern "C" fn glue_start( return oak_abi::INVALID_HANDLE; } }; - info!( - "start runtime with initial config {}.{}", - app_config.initial_node_config_name, app_config.initial_entrypoint_name - ); + info!("start runtime with application config {:?}", app_config); let (runtime, grpc_handle) = match oak_runtime::configure_and_run(app_config) { Ok(p) => p, Err(status) => { diff --git a/oak/server/rust/oak_loader/Cargo.toml b/oak/server/rust/oak_loader/Cargo.toml index da4a2eb3fd4..4cf0aaa9b18 100644 --- a/oak/server/rust/oak_loader/Cargo.toml +++ b/oak/server/rust/oak_loader/Cargo.toml @@ -9,5 +9,6 @@ license = "Apache-2.0" env_logger = "*" log = "*" oak_runtime = "=0.1.0" +oak_abi = "=0.1.0" prost = "*" structopt = "*" diff --git a/oak/server/rust/oak_loader/src/main.rs b/oak/server/rust/oak_loader/src/main.rs index 8c1b5d17a23..f786c191c2f 100644 --- a/oak/server/rust/oak_loader/src/main.rs +++ b/oak/server/rust/oak_loader/src/main.rs @@ -23,7 +23,8 @@ //! ``` use log::{error, info}; -use oak_runtime::{configure_and_run, metrics, proto::oak::application::ApplicationConfiguration}; +use oak_abi::proto::oak::application::ApplicationConfiguration; +use oak_runtime::{configure_and_run, metrics}; use prost::Message; use std::{ fs::File, @@ -68,7 +69,7 @@ fn start_runtime(application: String) -> Result<(), Box> let app_config = { let buffer = read_file(&application)?; - ApplicationConfiguration::decode(&buffer[..]) + ApplicationConfiguration::decode(buffer.as_ref()) .map_err(|error| format!("Failed to decode application configuration: {:?}", error))? }; diff --git a/oak/server/rust/oak_runtime/build.rs b/oak/server/rust/oak_runtime/build.rs deleted file mode 100644 index e43d273db50..00000000000 --- a/oak/server/rust/oak_runtime/build.rs +++ /dev/null @@ -1,24 +0,0 @@ -// -// Copyright 2019 The Project Oak Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -fn main() { - // TODO(#850): get Prost code generation working in Bazel. - oak_utils::compile_protos_to( - &["../../../../oak/proto/application.proto"], - &["../../../../oak/proto"], - "src/proto/", - ); -} diff --git a/oak/server/rust/oak_runtime/src/config.rs b/oak/server/rust/oak_runtime/src/config.rs index c5c1127c3ec..15ed1fa8753 100644 --- a/oak/server/rust/oak_runtime/src/config.rs +++ b/oak/server/rust/oak_runtime/src/config.rs @@ -14,23 +14,12 @@ // limitations under the License. // -use crate::proto::oak::application::{ - node_configuration::ConfigType, ApplicationConfiguration, GrpcServerConfiguration, - LogConfiguration, NodeConfiguration, WebAssemblyConfiguration, -}; -use itertools::Itertools; -use std::{collections::HashMap, net::AddrParseError, sync::Arc}; - -use log::{error, warn}; - -use oak_abi::OakStatus; - -use crate::{ - node, - node::{check_port, load_wasm}, - runtime, - runtime::{Handle, Runtime}, +use crate::runtime::{Handle, Runtime}; +use oak_abi::{ + proto::oak::application::{ApplicationConfiguration, NodeConfiguration}, + OakStatus, }; +use std::{collections::HashMap, sync::Arc}; /// Create an application configuration. /// @@ -38,88 +27,17 @@ use crate::{ /// - logger_name: Node name to use for a logger configuration; if empty no logger will be included. /// - initial_node: Initial Node to run on launch. /// - entrypoint: Entrypoint in the initial Node to run on launch. -pub fn application_configuration( - module_name_wasm: HashMap, S>, - logger_name: &str, - initial_node: &str, - entrypoint: &str, +pub fn application_configuration( + wasm_modules: HashMap>, + initial_node_configuration: &NodeConfiguration, ) -> ApplicationConfiguration { - let mut nodes: Vec = module_name_wasm - .into_iter() - .sorted() - .map(|(name, wasm)| NodeConfiguration { - name, - config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration { - module_bytes: wasm, - })), - }) - .collect(); - - if !logger_name.is_empty() { - nodes.push(NodeConfiguration { - name: logger_name.to_string(), - config_type: Some(ConfigType::LogConfig(LogConfiguration {})), - }); - } - ApplicationConfiguration { - node_configs: nodes, - initial_node_config_name: initial_node.into(), - initial_entrypoint_name: entrypoint.into(), - ..Default::default() + wasm_modules, + initial_node_configuration: Some(initial_node_configuration.clone()), + grpc_port: 0, } } -/// Load a `runtime::Configuration` from a protobuf `ApplicationConfiguration`. -/// This can fail if an unsupported Node is passed, or if a Node was unable to be initialized with -/// the given configuration. -pub fn from_protobuf( - app_config: ApplicationConfiguration, -) -> Result { - let mut config = runtime::Configuration { - nodes: HashMap::new(), - entry_module: app_config.initial_node_config_name.clone(), - entrypoint: app_config.initial_entrypoint_name.clone(), - }; - - for node in app_config.node_configs { - config.nodes.insert( - node.name.clone(), - match &node.config_type { - None => { - error!("Node config {} with no type", node.name); - return Err(OakStatus::ErrInvalidArgs); - } - Some(ConfigType::LogConfig(_)) => node::Configuration::LogNode, - Some(ConfigType::GrpcServerConfig(GrpcServerConfiguration { address })) => address - .parse() - .map_err(|error: AddrParseError| error.into()) - .and_then(|address| check_port(&address).map(|_| address)) - .map(|address| node::Configuration::GrpcServerNode { address }) - .map_err(|error| { - error!("Incorrect gRPC server address: {:?}", error); - OakStatus::ErrInvalidArgs - })?, - Some(ConfigType::WasmConfig(WebAssemblyConfiguration { module_bytes, .. })) => { - load_wasm(&module_bytes).map_err(|e| { - error!("Error loading Wasm module: {}", e); - OakStatus::ErrInvalidArgs - })? - } - Some(node_config) => { - warn!( - "Assuming pseudo-Node of type {:?} implemented externally!", - node_config - ); - node::Configuration::External - } - }, - ); - } - - Ok(config) -} - /// Configure a [`Runtime`] from the given protobuf [`ApplicationConfiguration`] and begin /// execution. This returns an [`Arc`] reference to the created [`Runtime`], and a writeable /// [`Handle`] to send messages into the Runtime. Creating a new channel and passing the write @@ -127,8 +45,7 @@ pub fn from_protobuf( pub fn configure_and_run( app_config: ApplicationConfiguration, ) -> Result<(Arc, Handle), OakStatus> { - let configuration = from_protobuf(app_config)?; - let runtime = Arc::new(Runtime::create(configuration)); + let runtime = Arc::new(Runtime::create(app_config)); let handle = runtime.clone().run()?; Ok((runtime, handle)) } diff --git a/oak/server/rust/oak_runtime/src/lib.rs b/oak/server/rust/oak_runtime/src/lib.rs index a4caf44a5b3..360c6c35784 100644 --- a/oak/server/rust/oak_runtime/src/lib.rs +++ b/oak/server/rust/oak_runtime/src/lib.rs @@ -23,8 +23,6 @@ //! be enabled in development, as it destroys the privacy guarantees of the //! platform by providing easy channels for the exfiltration of private data. -pub mod proto; - pub mod config; pub mod message; pub mod metrics; diff --git a/oak/server/rust/oak_runtime/src/node/grpc_server.rs b/oak/server/rust/oak_runtime/src/node/grpc_server.rs index e85b1acb053..71ab2743452 100644 --- a/oak/server/rust/oak_runtime/src/node/grpc_server.rs +++ b/oak/server/rust/oak_runtime/src/node/grpc_server.rs @@ -14,7 +14,17 @@ // limitations under the License. // +use crate::{ + metrics::METRICS, node::ConfigurationError, pretty_name_for_thread, runtime::RuntimeProxy, + Handle, +}; use log::{error, info, warn}; +use oak_abi::{ + grpc::encap_request, + label::Label, + proto::oak::{application::GrpcServerConfiguration, encap::GrpcRequest}, + ChannelReadStatus, OakStatus, +}; use prost::Message; use prost_types::Any; use std::{ @@ -23,23 +33,18 @@ use std::{ thread::{self, JoinHandle}, }; -use oak_abi::{ - grpc::encap_request, label::Label, proto::oak::encap::GrpcRequest, ChannelReadStatus, OakStatus, -}; - -use crate::{metrics::METRICS, pretty_name_for_thread, runtime::RuntimeProxy, Handle}; - /// Struct that represents a gRPC server pseudo-Node. /// /// For each gRPC request from a client, gRPC server pseudo-Node creates a pair of temporary /// channels (to write a request to and to read a response from) and passes corresponding handles to /// the [`GrpcServerNode::channel_writer`]. +#[derive(Clone)] pub struct GrpcServerNode { /// Pseudo-Node name that corresponds to an entry from the /// [`ApplicationConfiguration`]. /// /// [`ApplicationConfiguration`]: crate::proto::oak::application::ApplicationConfiguration - config_name: String, + display_name: String, /// Reference to a Runtime that corresponds to a Node that created a gRPC server pseudo-Node. runtime: RuntimeProxy, /// Server address to listen client requests on. @@ -68,22 +73,18 @@ impl Into for GrpcServerError { } } -/// Clone implementation without `thread_handle` copying to pass the Node to other threads. -impl Clone for GrpcServerNode { - fn clone(&self) -> Self { - Self { - config_name: self.config_name.to_string(), - runtime: self.runtime.clone(), - address: self.address, - initial_reader: self.initial_reader, - channel_writer: self.channel_writer, - } +impl Display for GrpcServerNode { + fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { + write!(f, "GrpcServerNode({})", self.display_name) } } -impl Display for GrpcServerNode { - fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - write!(f, "GrpcServerNode({})", self.config_name) +/// Checks if port is greater than 1023. +pub fn check_port(address: &SocketAddr) -> Result<(), ConfigurationError> { + if address.port() > 1023 { + Ok(()) + } else { + Err(ConfigurationError::IncorrectPort) } } @@ -94,18 +95,20 @@ impl GrpcServerNode { /// their values after the gRPC server pseudo-Node has started and a separate thread was /// initialized. pub fn new( - config_name: &str, + display_name: &str, + config: GrpcServerConfiguration, runtime: RuntimeProxy, - address: SocketAddr, initial_reader: Handle, - ) -> Self { - Self { - config_name: config_name.to_string(), + ) -> Result { + let address = config.address.parse()?; + check_port(&address)?; + Ok(Self { + display_name: display_name.to_string(), runtime, address, initial_reader, channel_writer: None, - } + }) } /// Reads a [`Handle`] from a channel specified by [`GrpcServerNode::initial_reader`]. diff --git a/oak/server/rust/oak_runtime/src/node/logger.rs b/oak/server/rust/oak_runtime/src/node/logger.rs index 3dffd505076..50caa87ade8 100644 --- a/oak/server/rust/oak_runtime/src/node/logger.rs +++ b/oak/server/rust/oak_runtime/src/node/logger.rs @@ -14,34 +14,33 @@ // limitations under the License. // -use std::{ - fmt::{self, Display, Formatter}, - string::String, -}; - -use log::{error, info, log}; - -use oak_abi::OakStatus; -use std::thread::{self, JoinHandle}; - use crate::{ pretty_name_for_thread, runtime::{Handle, RuntimeProxy}, }; -use oak_abi::proto::oak::log::{Level, LogMessage}; +use log::{error, info, log}; +use oak_abi::{ + proto::oak::log::{Level, LogMessage}, + OakStatus, +}; use prost::Message; +use std::{ + fmt::{self, Display, Formatter}, + string::String, + thread::{self, JoinHandle}, +}; pub struct LogNode { - config_name: String, + name: String, runtime: RuntimeProxy, reader: Handle, } impl LogNode { /// Creates a new [`LogNode`] instance, but does not start it. - pub fn new(config_name: &str, runtime: RuntimeProxy, reader: Handle) -> Self { + pub fn new(name: &str, runtime: RuntimeProxy, reader: Handle) -> Self { Self { - config_name: config_name.to_string(), + name: name.to_string(), runtime, reader, } @@ -64,7 +63,7 @@ impl LogNode { let _ = self.runtime.wait_on_channels(&[self.reader]); if let Some(message) = self.runtime.channel_read(self.reader)? { - match LogMessage::decode(&*message.data) { + match LogMessage::decode(message.data.as_ref()) { Ok(msg) => log!( target: &format!("{}:{}", msg.file, msg.line), to_level(msg.level), @@ -80,7 +79,7 @@ impl LogNode { impl Display for LogNode { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - write!(f, "LogNode({})", self.config_name) + write!(f, "LogNode({})", self.name) } } diff --git a/oak/server/rust/oak_runtime/src/node/mod.rs b/oak/server/rust/oak_runtime/src/node/mod.rs index 8d850aa3eb1..7f5b2566a6e 100644 --- a/oak/server/rust/oak_runtime/src/node/mod.rs +++ b/oak/server/rust/oak_runtime/src/node/mod.rs @@ -14,16 +14,12 @@ // limitations under the License. // -use std::{ - net::{AddrParseError, SocketAddr}, - string::String, - sync::Arc, - thread::JoinHandle, -}; - -use oak_abi::OakStatus; - use crate::{runtime::RuntimeProxy, Handle}; +use oak_abi::{ + proto::oak::application::{ApplicationConfiguration, LogConfiguration, NodeConfiguration}, + OakStatus, +}; +use std::{net::AddrParseError, thread::JoinHandle}; pub mod external; mod grpc_server; @@ -45,31 +41,12 @@ pub trait Node { fn start(self: Box) -> Result, OakStatus>; } -/// A `Configuration` corresponds to an entry from a `ApplicationConfiguration`. It is the -/// static implementation specific configuration shared between instances. -pub enum Configuration { - /// The configuration for a logging pseudo-Node. - LogNode, - - /// The configuration for a gRPC server pseudo-Node that contains an `address` to listen on. - GrpcServerNode { address: SocketAddr }, - - /// The configuration for a Wasm Node. - // It would be better to store a list of exported methods and copyable Wasm interpreter - // instance, but wasmi doesn't allow this. We make do with having a copyable - // `Arc` that we pass to new Nodes, and clone to to spawn new - // `wasmi::ModuleInstances`. - WasmNode { module: Arc }, - - /// The configuration for an externally provided pseudo-Node. - External, -} - -/// A enumeration for errors occuring when building `Configuration` from protobuf types. +/// A enumeration for errors occuring when creating a new [`Node`] instance. #[derive(Debug)] pub enum ConfigurationError { AddressParsingError(AddrParseError), IncorrectPort, + IncorrectWebAssemblyModuleName, WasmiModuleInializationError(wasmi::Error), } @@ -86,6 +63,9 @@ impl std::fmt::Display for ConfigurationError { write!(f, "Failed to parse an address: {}", e) } ConfigurationError::IncorrectPort => write!(f, "Incorrect port (must be > 1023)"), + ConfigurationError::IncorrectWebAssemblyModuleName => { + write!(f, "Incorrect WebAssembly module name") + } ConfigurationError::WasmiModuleInializationError(e) => { write!(f, "Failed to initialize wasmi::Module: {}", e) } @@ -93,56 +73,43 @@ impl std::fmt::Display for ConfigurationError { } } -/// Loads a Wasm module into a Node configuration, returning an error if `wasmi` failed to load the -/// module. -pub fn load_wasm(wasm_bytes: &[u8]) -> Result { - let module = wasmi::Module::from_buffer(wasm_bytes) - .map_err(ConfigurationError::WasmiModuleInializationError)?; - Ok(Configuration::WasmNode { - module: Arc::new(module), - }) -} - -/// Checks if port is greater than 1023. -pub fn check_port(address: &SocketAddr) -> Result<(), ConfigurationError> { - if address.port() > 1023 { - Ok(()) - } else { - Err(ConfigurationError::IncorrectPort) - } -} - -impl Configuration { - /// Creates a new Node instance corresponding to the [`Configuration`] `self`. - /// - /// On success returns a boxed [`Node`] that may be started by invoking the [`Node::start`] - /// method. - pub fn create_node( - &self, - config_name: &str, // Used for pretty debugging - runtime: RuntimeProxy, - entrypoint: String, - initial_reader: Handle, - ) -> Box { - match self { - Configuration::LogNode => { - Box::new(logger::LogNode::new(config_name, runtime, initial_reader)) - } - Configuration::GrpcServerNode { address } => Box::new( - grpc_server::GrpcServerNode::new(config_name, runtime, *address, initial_reader), - ), - Configuration::WasmNode { module } => Box::new(wasm::WasmNode::new( - config_name, - runtime, - module.clone(), - entrypoint, - initial_reader, - )), - Configuration::External => Box::new(external::PseudoNode::new( - config_name, - runtime, - initial_reader, - )), - } +pub fn create_node( + application_configuration: &ApplicationConfiguration, + node_configuration: &NodeConfiguration, + runtime: RuntimeProxy, + initial_reader: Handle, +) -> Result, ConfigurationError> { + let name = &node_configuration.name; + use oak_abi::proto::oak::application::node_configuration::ConfigType; + match &node_configuration.config_type { + Some(ConfigType::LogConfig(LogConfiguration {})) => Ok(Box::new(logger::LogNode::new( + name, + runtime, + initial_reader, + ))), + Some(ConfigType::GrpcServerConfig(config)) => Ok(Box::new( + grpc_server::GrpcServerNode::new(name, config.clone(), runtime, initial_reader)?, + )), + Some(ConfigType::WasmConfig(config)) => Ok(Box::new(wasm::WasmNode::new( + name, + application_configuration, + config.clone(), + runtime, + initial_reader, + )?)), + Some(ConfigType::GrpcClientConfig(_config)) => Ok(Box::new(external::PseudoNode::new( + name, + runtime, + initial_reader, + ))), + Some(ConfigType::RoughtimeClientConfig(_config)) => Ok(Box::new( + external::PseudoNode::new(name, runtime, initial_reader), + )), + Some(ConfigType::StorageConfig(_config)) => Ok(Box::new(external::PseudoNode::new( + name, + runtime, + initial_reader, + ))), + None => Err(ConfigurationError::IncorrectPort), } } diff --git a/oak/server/rust/oak_runtime/src/node/wasm/mod.rs b/oak/server/rust/oak_runtime/src/node/wasm/mod.rs index 831ae26a01a..0327960aa94 100644 --- a/oak/server/rust/oak_runtime/src/node/wasm/mod.rs +++ b/oak/server/rust/oak_runtime/src/node/wasm/mod.rs @@ -14,8 +14,22 @@ // limitations under the License. // +use crate::{ + node::ConfigurationError, + pretty_name_for_thread, + runtime::{HandleDirection, ReadStatus, RuntimeProxy}, + Handle, Message, +}; use byteorder::{ByteOrder, LittleEndian}; use log::{debug, error, info, warn}; +use oak_abi::{ + label::Label, + proto::oak::application::{ + ApplicationConfiguration, NodeConfiguration, WebAssemblyConfiguration, + }, + ChannelReadStatus, OakStatus, +}; +use prost::Message as _; use rand::RngCore; use std::{ collections::HashMap, @@ -27,13 +41,6 @@ use std::{ }; use wasmi::ValueType; -use crate::{ - pretty_name_for_thread, - runtime::{Handle, HandleDirection, ReadStatus, RuntimeProxy}, - Message, -}; -use oak_abi::{label::Label, ChannelReadStatus, OakStatus}; - #[cfg(test)] mod tests; @@ -125,12 +132,12 @@ impl WasmInterface { /// Creates a new `WasmInterface` structure. pub fn new( - pretty_name: String, + display_name: String, runtime: RuntimeProxy, initial_reader: Handle, ) -> (WasmInterface, oak_abi::Handle) { let mut interface = WasmInterface { - pretty_name, + pretty_name: display_name, readers: HashMap::new(), writers: HashMap::new(), memory: None, @@ -140,27 +147,20 @@ impl WasmInterface { (interface, handle) } - /// Corresponds to the host ABI function [`node_create: (usize, usize, usize, usize, - /// u64) -> u32`](oak_abi::node_create). + /// Corresponds to the host ABI function [`node_create: (usize, usize, u64) -> + /// u32`](oak_abi::node_create). #[allow(clippy::too_many_arguments)] fn node_create( &self, - name_ptr: AbiPointer, - name_length: AbiPointerOffset, - entrypoint_ptr: AbiPointer, - entrypoint_length: AbiPointerOffset, + config_ptr: AbiPointer, + config_length: AbiPointerOffset, label_ptr: AbiPointer, label_length: AbiPointerOffset, initial_handle: oak_abi::Handle, ) -> Result<(), OakStatus> { debug!( - "{}: node_create({}, {}, {}, {}, {})", - self.pretty_name, - name_ptr, - name_length, - entrypoint_ptr, - entrypoint_length, - initial_handle + "{}: node_create({}, {}, {})", + self.pretty_name, config_ptr, config_length, initial_handle ); if self.runtime.is_terminating() { @@ -168,37 +168,20 @@ impl WasmInterface { return Err(OakStatus::ErrTerminated); } - let config_name_bytes = self + let config_bytes = self .get_memory() - .get(name_ptr, name_length as usize) + .get(config_ptr, config_length as usize) .map_err(|err| { error!( - "{}: node_create(): Unable to read name from guest memory: {:?}", + "{}: node_create(): Unable to read config from guest memory: {:?}", self.pretty_name, err ); OakStatus::ErrInvalidArgs })?; - let config_name = String::from_utf8(config_name_bytes).map_err(|err| { - error!( - "{}: node_create(): Unable to parse config_name: {:?}", - self.pretty_name, err - ); - OakStatus::ErrInvalidArgs - })?; - let entrypoint_bytes = self - .get_memory() - .get(entrypoint_ptr, entrypoint_length as usize) - .map_err(|err| { - error!( - "{}: node_create(): Unable to read entrypoint from guest memory: {:?}", - self.pretty_name, err - ); - OakStatus::ErrInvalidArgs - })?; - let entrypoint = String::from_utf8(entrypoint_bytes).map_err(|err| { - error!( - "{}: node_create(): Unable to parse entrypoint: {:?}", + let config = NodeConfiguration::decode(config_bytes.as_ref()).map_err(|err| { + warn!( + "{}: node_create(): Could not parse node configuration: {:?}", self.pretty_name, err ); OakStatus::ErrInvalidArgs @@ -223,8 +206,8 @@ impl WasmInterface { })?; debug!( - "{}: node_create('{}', '{}', {:?})", - self.pretty_name, config_name, entrypoint, label + "{}: node_create({:?}, {:?})", + self.pretty_name, config, label ); let channel_ref = self.readers.get(&initial_handle).ok_or(()).map_err(|_| { error!( @@ -235,11 +218,11 @@ impl WasmInterface { })?; self.runtime - .node_create(&config_name, &entrypoint, &label, *channel_ref) - .map_err(|_| { + .node_create(&config, &label, *channel_ref) + .map_err(|err| { error!( - "{}: node_create(): Config \"{}\" entrypoint \"{}\" not found", - self.pretty_name, config_name, entrypoint + "{}: node_create(): Could not create node: {:?}", + self.pretty_name, err ); OakStatus::ErrInvalidArgs }) @@ -626,8 +609,6 @@ impl wasmi::Externals for WasmInterface { args.nth_checked(2)?, args.nth_checked(3)?, args.nth_checked(4)?, - args.nth_checked(5)?, - args.nth_checked(6)?, )), RANDOM_GET => { map_host_errors(self.random_get(args.nth_checked(0)?, args.nth_checked(1)?)) @@ -677,8 +658,6 @@ impl wasmi::ModuleImportResolver for WasmInterface { &[ ABI_USIZE, // config_buf ABI_USIZE, // config_len - ABI_USIZE, // entrypoint_buf - ABI_USIZE, // entrypoint_len ABI_USIZE, // label_buf ABI_USIZE, // label_len ValueType::I64, // handle @@ -844,34 +823,40 @@ impl wasmi::ModuleImportResolver for WasiStub { } pub struct WasmNode { - config_name: String, + display_name: String, runtime: RuntimeProxy, module: Arc, - entrypoint: String, + entrypoint_name: String, reader: Handle, } impl WasmNode { /// Creates a new [`WasmNode`] instance, but does not start it. pub fn new( - config_name: &str, + display_name: &str, + application_configuration: &ApplicationConfiguration, + node_configuration: WebAssemblyConfiguration, runtime: RuntimeProxy, - module: Arc, - entrypoint: String, reader: Handle, - ) -> Self { - Self { - config_name: config_name.to_string(), + ) -> Result { + let wasm_module_bytes = application_configuration + .wasm_modules + .get(&node_configuration.wasm_module_name) + .ok_or(ConfigurationError::IncorrectWebAssemblyModuleName)?; + let module = wasmi::Module::from_buffer(&wasm_module_bytes) + .map_err(ConfigurationError::WasmiModuleInializationError)?; + Ok(Self { + display_name: display_name.to_string(), runtime, - module, - entrypoint, + module: Arc::new(module), + entrypoint_name: node_configuration.wasm_entrypoint_name.to_string(), reader, - } + }) } fn validate_entrypoint(&self) -> Result<(), OakStatus> { let (abi, _) = - WasmInterface::new(self.config_name.clone(), self.runtime.clone(), self.reader); + WasmInterface::new(self.display_name.clone(), self.runtime.clone(), self.reader); let wasi_stub = WasiStub; let instance = wasmi::ModuleInstance::new( &self.module, @@ -884,24 +869,29 @@ impl WasmNode { let expected_signature = wasmi::Signature::new(&[ValueType::I64][..], None); - let export = instance.export_by_name(&self.entrypoint).ok_or_else(|| { - warn!("entrypoint '{}' export not found", self.entrypoint); - OakStatus::ErrInvalidArgs - })?; + let export = instance + .export_by_name(&self.entrypoint_name) + .ok_or_else(|| { + warn!("entrypoint '{}' export not found", self.entrypoint_name); + OakStatus::ErrInvalidArgs + })?; let export_func = export.as_func().ok_or_else(|| { - warn!("entrypoint '{}' export is not a function", self.entrypoint); + warn!( + "entrypoint '{}' export is not a function", + self.entrypoint_name + ); OakStatus::ErrInvalidArgs })?; let export_func_signature = export_func.signature(); if export_func_signature == &expected_signature { - info!("entrypoint '{}' export validated", self.entrypoint); + info!("entrypoint '{}' export validated", self.entrypoint_name); Ok(()) } else { warn!( "entrypoint '{}' export has incorrect function signature: {:?}", - self.entrypoint, export_func_signature + self.entrypoint_name, export_func_signature ); Err(OakStatus::ErrInvalidArgs) } @@ -931,7 +921,7 @@ impl WasmNode { instance .invoke_export( - &self.entrypoint, + &self.entrypoint_name, &[wasmi::RuntimeValue::I64(initial_handle as i64)], &mut abi, ) @@ -943,7 +933,7 @@ impl WasmNode { impl Display for WasmNode { fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> { - write!(f, "WasmNode({})", self.config_name) + write!(f, "WasmNode({})", self.display_name) } } @@ -953,8 +943,8 @@ impl super::Node for WasmNode { /// If the entry point is not found, returns `Err(OakStatus::ErrInvalidArgs)` immediately. fn start(self: Box) -> Result, OakStatus> { debug!( - "Node::start(): discovering '{}' '{}'", - self.config_name, self.entrypoint + "{}: Node::start(): discovering entrypoint '{}'", + self, self.entrypoint_name ); // wasmi can't enumerate exports at creation, so we have to do it here per instance spawned @@ -963,8 +953,8 @@ impl super::Node for WasmNode { self.validate_entrypoint()?; debug!( - "Node::start(): starting '{}' '{}'", - self.config_name, self.entrypoint + "{}: Node::start(): starting entrypoint '{}'", + self, self.entrypoint_name ); let thread_handle = thread::Builder::new() diff --git a/oak/server/rust/oak_runtime/src/node/wasm/tests.rs b/oak/server/rust/oak_runtime/src/node/wasm/tests.rs index 443e82c9862..06179d9867f 100644 --- a/oak/server/rust/oak_runtime/src/node/wasm/tests.rs +++ b/oak/server/rust/oak_runtime/src/node/wasm/tests.rs @@ -16,34 +16,41 @@ use super::*; use crate::runtime::{Runtime, TEST_NODE_ID}; -use oak_abi::label::Label; +use maplit::hashmap; +use oak_abi::{ + label::Label, + proto::oak::application::{ApplicationConfiguration, WebAssemblyConfiguration}, +}; use wat::{parse_file, parse_str}; -fn start_node>(buffer: S, entrypoint: &str) -> Result<(), OakStatus> { - let configuration = crate::runtime::Configuration { - nodes: HashMap::new(), - entry_module: "test_module".to_string(), - entrypoint: entrypoint.to_string(), +fn start_node(buffer: Vec, entrypoint_name: &str) -> Result<(), OakStatus> { + let module_name = "oak_module"; + let configuration = ApplicationConfiguration { + wasm_modules: hashmap! { module_name.to_string() => buffer }, + initial_node_configuration: None, + grpc_port: 0, }; - let runtime_ref = Arc::new(Runtime::create(configuration)); + let runtime_ref = Arc::new(Runtime::create(configuration.clone())); let (_, reader_handle) = runtime_ref.new_channel(TEST_NODE_ID, &Label::public_trusted()); let runtime_proxy = runtime_ref.clone().new_runtime_proxy(); - let module = wasmi::Module::from_buffer(buffer).unwrap(); - - let node = Box::new(WasmNode::new( + let node = WasmNode::new( "test", + &configuration, + WebAssemblyConfiguration { + wasm_module_name: module_name.to_string(), + wasm_entrypoint_name: entrypoint_name.to_string(), + }, runtime_proxy, - Arc::new(module), - entrypoint.to_string(), reader_handle, - )); + ) + .expect("could not create WasmNode"); let result = runtime_ref.node_start_instance( TEST_NODE_ID, "test.node".to_string(), - node, + Box::new(node), &oak_abi::label::Label::public_trusted(), vec![], ); diff --git a/oak/server/rust/oak_runtime/src/proto/mod.rs b/oak/server/rust/oak_runtime/src/proto/mod.rs deleted file mode 100644 index 013eb5623df..00000000000 --- a/oak/server/rust/oak_runtime/src/proto/mod.rs +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright 2019 The Project Oak Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -pub mod oak { - pub mod application { - include!("oak.application.rs"); - } -} diff --git a/oak/server/rust/oak_runtime/src/runtime/mod.rs b/oak/server/rust/oak_runtime/src/runtime/mod.rs index 194495995f7..b3b0800ff36 100644 --- a/oak/server/rust/oak_runtime/src/runtime/mod.rs +++ b/oak/server/rust/oak_runtime/src/runtime/mod.rs @@ -14,9 +14,15 @@ // limitations under the License. // +use crate::{message::Message, metrics::METRICS, node, pretty_name_for_thread}; use core::sync::atomic::{AtomicBool, AtomicU64, Ordering::SeqCst}; use itertools::Itertools; -use log::{debug, error, info, trace}; +use log::{debug, error, info, trace, warn}; +use oak_abi::{ + label::Label, + proto::oak::application::{ApplicationConfiguration, NodeConfiguration}, + ChannelReadStatus, OakStatus, +}; use std::{ collections::{HashMap, HashSet}, fmt::Write, @@ -26,9 +32,6 @@ use std::{ thread::JoinHandle, }; -use crate::{message::Message, metrics::METRICS, node, pretty_name_for_thread}; -use oak_abi::{label::Label, ChannelReadStatus, OakStatus}; - mod channel; #[cfg(feature = "oak_debug")] mod introspect; @@ -108,12 +111,6 @@ pub const RUNTIME_NODE_ID: NodeId = NodeId(0); #[cfg(any(feature = "test_build", test))] pub const TEST_NODE_ID: NodeId = NodeId(0); -pub struct Configuration { - pub nodes: HashMap, - pub entry_module: String, - pub entrypoint: String, -} - /// A helper type to determine if `try_read_message` was called with not enough `bytes_capacity` /// and/or `handles_capacity`. pub enum ReadStatus { @@ -123,7 +120,7 @@ pub enum ReadStatus { /// Runtime structure for configuring and running a set of Oak Nodes. pub struct Runtime { - configuration: Configuration, + configuration: ApplicationConfiguration, terminating: AtomicBool, channels: channel::ChannelMapping, @@ -140,7 +137,7 @@ pub struct Runtime { impl Runtime { /// Creates a [`Runtime`] instance but does not start executing any Node. - pub fn create(configuration: Configuration) -> Self { + pub fn create(configuration: ApplicationConfiguration) -> Self { Self { configuration, terminating: AtomicBool::new(false), @@ -163,8 +160,11 @@ impl Runtime { /// creating a new channel and passing the write [`Handle`] into the Runtime will enable /// messages to be read back out. pub fn run(self: Arc) -> Result { - let module_name = self.configuration.entry_module.clone(); - let entrypoint = self.configuration.entrypoint.clone(); + let node_configuration = self + .configuration + .initial_node_configuration + .as_ref() + .ok_or(OakStatus::ErrInvalidArgs)?; if cfg!(feature = "oak_debug") { // TODO(#672): make introspection port configurable externally @@ -183,8 +183,7 @@ impl Runtime { self.clone().node_create( RUNTIME_NODE_ID, - &module_name, - &entrypoint, + &node_configuration, // When first starting, we assign the least privileged label to the entry point Node. &Label::public_trusted(), chan_reader, @@ -947,8 +946,7 @@ impl Runtime { pub fn node_create( self: Arc, node_id: NodeId, - module_name: &str, - entrypoint: &str, + config: &NodeConfiguration, label: &Label, reader: Handle, ) -> Result<(), OakStatus> { @@ -971,23 +969,14 @@ impl Runtime { let reader = self.channels.duplicate_reference(reader)?; - let instance = self - .configuration - .nodes - .get(module_name) - .ok_or(OakStatus::ErrInvalidArgs) - .map(|conf| { - // This only creates a Node instance, but does not start it. - conf.create_node(module_name, runtime_proxy, entrypoint.to_owned(), reader) + // This only creates a Node instance, but does not start it. + let instance = node::create_node(&self.configuration, config, runtime_proxy, reader) + .map_err(|err| { + warn!("Could not create node: {:?}", err); + OakStatus::ErrInvalidArgs })?; - self.node_start_instance( - node_id, - format!("{}.{}", module_name, entrypoint), - instance, - label, - vec![reader], - )?; + self.node_start_instance(node_id, config.name.clone(), instance, label, vec![reader])?; Ok(()) } @@ -1099,18 +1088,13 @@ impl RuntimeProxy { /// See [`Runtime::node_create`]. pub fn node_create( &self, - module_name: &str, - entrypoint: &str, + config: &NodeConfiguration, label: &Label, channel_read_handle: Handle, ) -> Result<(), OakStatus> { - self.runtime.clone().node_create( - self.node_id, - module_name, - entrypoint, - label, - channel_read_handle, - ) + self.runtime + .clone() + .node_create(self.node_id, config, label, channel_read_handle) } /// See [`Runtime::new_channel`]. diff --git a/oak/server/rust/oak_runtime/src/runtime/tests.rs b/oak/server/rust/oak_runtime/src/runtime/tests.rs index 83aab8086a9..b6f9836a14f 100644 --- a/oak/server/rust/oak_runtime/src/runtime/tests.rs +++ b/oak/server/rust/oak_runtime/src/runtime/tests.rs @@ -15,18 +15,20 @@ // use super::*; +use maplit::hashmap; +use oak_abi::proto::oak::application::{ + node_configuration::ConfigType, ApplicationConfiguration, LogConfiguration, NodeConfiguration, +}; type NodeBody = dyn Fn(&RuntimeProxy) -> Result<(), OakStatus> + Send + Sync; /// Runs the provided function as if it were the body of a [`Node`] implementation, which is /// instantiated by the [`Runtime`] with the provided [`Label`]. fn run_node_body(node_label: Label, node_body: Box) { - let configuration = crate::runtime::Configuration { - nodes: maplit::hashmap![ - "log".to_string() => crate::node::Configuration::LogNode, - ], - entry_module: "test_module".to_string(), - entrypoint: "test_function".to_string(), + let configuration = ApplicationConfiguration { + wasm_modules: hashmap! {}, + initial_node_configuration: None, + grpc_port: 0, }; let runtime = Arc::new(crate::runtime::Runtime::create(configuration)); @@ -88,10 +90,15 @@ fn create_node_success() { Label::public_trusted(), Box::new(|runtime| { let (_write_handle, read_handle) = runtime.channel_create(&Label::public_trusted()); - let result = - runtime - .clone() - .node_create("log", "unused", &Label::public_trusted(), read_handle); + let node_configuration = NodeConfiguration { + name: "test".to_string(), + config_type: Some(ConfigType::LogConfig(LogConfiguration {})), + }; + let result = runtime.clone().node_create( + &node_configuration, + &Label::public_trusted(), + read_handle, + ); assert_eq!(Ok(()), result); Ok(()) }), @@ -105,9 +112,12 @@ fn create_node_invalid_configuration() { Label::public_trusted(), Box::new(|runtime| { let (_write_handle, read_handle) = runtime.channel_create(&Label::public_trusted()); + let node_configuration = NodeConfiguration { + name: "test".to_string(), + config_type: None, + }; let result = runtime.clone().node_create( - "invalid-configuration-name", - "unused", + &node_configuration, &Label::public_trusted(), read_handle, ); @@ -134,10 +144,15 @@ fn create_node_more_public_label() { secret_label, Box::new(|runtime| { let (_write_handle, read_handle) = runtime.channel_create(&Label::public_trusted()); - let result = - runtime - .clone() - .node_create("log", "unused", &Label::public_trusted(), read_handle); + let node_configuration = NodeConfiguration { + name: "test".to_string(), + config_type: Some(ConfigType::LogConfig(LogConfiguration {})), + }; + let result = runtime.clone().node_create( + &node_configuration, + &Label::public_trusted(), + read_handle, + ); assert_eq!(Err(OakStatus::ErrPermissionDenied), result); Ok(()) }), diff --git a/oak/server/rust/oak_runtime/tests/config_tests.rs b/oak/server/rust/oak_runtime/tests/config_tests.rs deleted file mode 100644 index 2053863a1db..00000000000 --- a/oak/server/rust/oak_runtime/tests/config_tests.rs +++ /dev/null @@ -1,115 +0,0 @@ -// -// Copyright 2020 The Project Oak Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -use maplit::hashmap; - -use oak_runtime::proto::oak::application::{ - node_configuration::ConfigType::{LogConfig, WasmConfig}, - ApplicationConfiguration, LogConfiguration, NodeConfiguration, WebAssemblyConfiguration, -}; - -#[test] -fn test_app_config() { - let cfg = oak_runtime::application_configuration( - hashmap!["node".to_string() => vec![0x00, 0x01]], - "lumberjack", - "node", - "main", - ); - assert_eq!( - ApplicationConfiguration { - node_configs: vec![ - NodeConfiguration { - name: "node".to_string(), - config_type: Some(WasmConfig(WebAssemblyConfiguration { - module_bytes: vec![0, 1] - })) - }, - NodeConfiguration { - name: "lumberjack".to_string(), - config_type: Some(LogConfig(LogConfiguration {})) - } - ], - initial_node_config_name: "node".to_string(), - initial_entrypoint_name: "main".to_string(), - grpc_port: 0 - }, - cfg - ); -} - -#[test] -fn test_app_config_multi() { - let cfg = oak_runtime::application_configuration( - hashmap![ - "node".to_string() => vec![0x00, 0x01], - "another_node".to_string() => vec![0x02, 0x03], - ], - "lumberjack", - "node", - "main", - ); - assert_eq!( - ApplicationConfiguration { - node_configs: vec![ - NodeConfiguration { - name: "another_node".to_string(), - config_type: Some(WasmConfig(WebAssemblyConfiguration { - module_bytes: vec![2, 3] - })) - }, - NodeConfiguration { - name: "node".to_string(), - config_type: Some(WasmConfig(WebAssemblyConfiguration { - module_bytes: vec![0, 1] - })) - }, - NodeConfiguration { - name: "lumberjack".to_string(), - config_type: Some(LogConfig(LogConfiguration {})) - } - ], - initial_node_config_name: "node".to_string(), - initial_entrypoint_name: "main".to_string(), - grpc_port: 0 - }, - cfg - ); -} - -#[test] -fn test_app_config_no_logger() { - let cfg = oak_runtime::application_configuration( - hashmap!["node".to_string() => vec![0x00, 0x01]], - "", - "node", - "main", - ); - assert_eq!( - ApplicationConfiguration { - node_configs: vec![NodeConfiguration { - name: "node".to_string(), - config_type: Some(WasmConfig(WebAssemblyConfiguration { - module_bytes: vec![0, 1] - })) - },], - initial_node_config_name: "node".to_string(), - initial_entrypoint_name: "main".to_string(), - grpc_port: 0 - }, - cfg - ); -} diff --git a/oak/server/rust/oak_runtime/tests/integration_test.rs b/oak/server/rust/oak_runtime/tests/integration_test.rs index 48a4e612eb0..1fbc0424451 100644 --- a/oak/server/rust/oak_runtime/tests/integration_test.rs +++ b/oak/server/rust/oak_runtime/tests/integration_test.rs @@ -20,13 +20,19 @@ use std::thread::spawn; const METRICS_PORT: u16 = 9876; mod common { - use oak_runtime::runtime::{Handle, Runtime}; - use hyper::{Client, Uri}; use log::info; use maplit::hashmap; - use oak_abi::OakStatus; - use oak_runtime::config; + use oak_abi::{ + proto::oak::application::{ + node_configuration::ConfigType, NodeConfiguration, WebAssemblyConfiguration, + }, + OakStatus, + }; + use oak_runtime::{ + config, + runtime::{Handle, Runtime}, + }; use std::sync::Arc; use wat::parse_str; @@ -44,11 +50,15 @@ mod common { // Create a runtime with one node let cfg = oak_runtime::application_configuration( hashmap![ - "node".to_string() => binary, + "module".to_string() => binary, ], - "lumberjack", - "node", - "oak_main", + &NodeConfiguration { + name: "test".to_string(), + config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration { + wasm_module_name: "module".to_string(), + wasm_entrypoint_name: "oak_main".to_string(), + })), + }, ); info!("Starting the runtime with one nodes."); @@ -67,7 +77,7 @@ mod common { info!("status: {}", res.status()); let buf = hyper::body::to_bytes(res).await?; - Ok(std::str::from_utf8(&buf[..]).unwrap().to_string()) + Ok(std::str::from_utf8(buf.as_ref()).unwrap().to_string()) } } diff --git a/sdk/rust/oak/src/grpc/client.rs b/sdk/rust/oak/src/grpc/client.rs index ed7c72f4bd1..91bb802a0b6 100644 --- a/sdk/rust/oak/src/grpc/client.rs +++ b/sdk/rust/oak/src/grpc/client.rs @@ -15,6 +15,7 @@ // use log::{info, warn}; +use oak_abi::proto::oak::application::NodeConfiguration; /// Client for a gRPC service in another Node. pub struct Client { @@ -27,26 +28,20 @@ impl Client { /// entrypoint name is required if this specifies another WebAssembly Oak Node, but is /// ignored if the Node configuration is for a gRPC client pseudo-Node (which acts as a /// proxy for a remote non-Oak gRPC service). - pub fn new(config_name: &str, entrypoint_name: &str) -> Option { + pub fn new(config: &NodeConfiguration) -> Option { let (invocation_sender, invocation_receiver) = crate::io::channel_create().expect("failed to create channel"); - let status = crate::node_create(config_name, entrypoint_name, invocation_receiver.handle); + let status = crate::node_create(config, invocation_receiver.handle); invocation_receiver .close() .expect("failed to close channel"); match status { Ok(_) => { - info!( - "Client created for '{}' in '{}'", - entrypoint_name, config_name - ); + info!("Client created for '{:?}'", config); Some(Client { invocation_sender }) } Err(status) => { - warn!( - "failed to create Client for '{}' in '{}' : {:?}", - entrypoint_name, config_name, status - ); + warn!("failed to create Client for '{:?}': {:?}", config, status); None } } diff --git a/sdk/rust/oak/src/grpc/mod.rs b/sdk/rust/oak/src/grpc/mod.rs index be2f7fab893..1829dff54ef 100644 --- a/sdk/rust/oak/src/grpc/mod.rs +++ b/sdk/rust/oak/src/grpc/mod.rs @@ -18,7 +18,12 @@ use crate::{OakError, OakStatus}; use log::error; -use oak_abi::proto::google::rpc; +use oak_abi::proto::{ + google::rpc, + oak::application::{ + node_configuration::ConfigType, GrpcServerConfiguration, NodeConfiguration, + }, +}; pub use oak_abi::proto::{ google::rpc::*, oak::encap::{GrpcRequest, GrpcResponse}, @@ -333,23 +338,21 @@ where node_fn(rr, writer) } -/// Default name for predefined Node configuration that corresponds to a gRPC pseudo-Node. -pub const DEFAULT_CONFIG_NAME: &str = "grpc_server"; - -/// Initialize a gRPC pseudo-Node with the default configuration. -pub fn init_default() { - init(DEFAULT_CONFIG_NAME).unwrap(); -} - /// Initializes a gRPC server pseudo-Node and passes it a handle to write invocations to. /// /// Returns a [`ReadHandle`] to read invocations from. /// /// [`ReadHandle`]: crate::ReadHandle -pub fn init(config: &str) -> std::result::Result { +pub fn init(address: &str) -> std::result::Result { // Create a channel and pass the read half to a new gRPC pseudo-Node. let (write_handle, read_handle) = crate::channel_create().expect("Couldn't create a channel"); - crate::node_create(config, "oak_main", read_handle)?; + let config = NodeConfiguration { + name: "grpc_server".to_string(), + config_type: Some(ConfigType::GrpcServerConfig(GrpcServerConfiguration { + address: address.to_string(), + })), + }; + crate::node_create(&config, read_handle)?; crate::channel_close(read_handle.handle).expect("Couldn't close a channel"); // Create a separate channel for receiving invocations and pass it to a gRPC pseudo-Node. diff --git a/sdk/rust/oak/src/lib.rs b/sdk/rust/oak/src/lib.rs index a215c54c56d..7be7a3f3d77 100644 --- a/sdk/rust/oak/src/lib.rs +++ b/sdk/rust/oak/src/lib.rs @@ -16,6 +16,8 @@ use byteorder::{ReadBytesExt, WriteBytesExt}; use log::{debug, error, info, warn}; +use oak_abi::proto::oak::application::NodeConfiguration; +use prost::Message; use serde::{Deserialize, Serialize}; // Re-export ABI constants that are also visible as part of the SDK API. @@ -29,6 +31,7 @@ pub use error::OakError; pub mod grpc; pub mod io; pub mod logger; +pub mod node_config; pub mod rand; pub mod roughtime; pub mod storage; @@ -37,7 +40,7 @@ pub mod proto { pub mod oak { // The storage protobuf messages use the label.Label type which is built // in the `oak_abi` crate, so make it available here too. - use oak_abi::proto::oak::label; + pub use oak_abi::proto::oak::{application, label}; pub mod storage { include!(concat!(env!("OUT_DIR"), "/oak.storage.rs")); } @@ -339,12 +342,8 @@ pub fn channel_close(handle: Handle) -> Result<(), OakStatus> { } /// Similar to [`node_create_with_label`], but with a fixed label corresponding to "public trusted". -pub fn node_create( - config_name: &str, - entrypoint_name: &str, - half: ReadHandle, -) -> Result<(), OakStatus> { - node_create_with_label(config_name, entrypoint_name, &Label::public_trusted(), half) +pub fn node_create(config: &NodeConfiguration, half: ReadHandle) -> Result<(), OakStatus> { + node_create_with_label(config, &Label::public_trusted(), half) } /// Creates a new Node running the configuration identified by `config_name`, running the entrypoint @@ -356,18 +355,20 @@ pub fn node_create( /// /// See https://github.com/project-oak/oak/blob/master/docs/concepts.md#labels pub fn node_create_with_label( - config_name: &str, - entrypoint_name: &str, + config: &NodeConfiguration, label: &Label, half: ReadHandle, ) -> Result<(), OakStatus> { let label_bytes = label.serialize(); + let mut config_bytes = Vec::new(); + config.encode(&mut config_bytes).map_err(|err| { + warn!("Could not encode node configuration: {:?}", err); + OakStatus::ErrInvalidArgs + })?; let status = unsafe { oak_abi::node_create( - config_name.as_ptr(), - config_name.len(), - entrypoint_name.as_ptr(), - entrypoint_name.len(), + config_bytes.as_ptr(), + config_bytes.len(), label_bytes.as_ptr(), label_bytes.len(), half.handle.id, @@ -413,7 +414,7 @@ pub fn set_panic_hook() { let msg = match payload.downcast_ref::<&'static str>() { Some(content) => *content, None => match payload.downcast_ref::() { - Some(content) => &content[..], + Some(content) => content.as_ref(), None => "", }, }; diff --git a/sdk/rust/oak/src/logger/mod.rs b/sdk/rust/oak/src/logger/mod.rs index f2264876659..db107bb4dc0 100644 --- a/sdk/rust/oak/src/logger/mod.rs +++ b/sdk/rust/oak/src/logger/mod.rs @@ -60,10 +60,6 @@ fn map_level(level: Level) -> oak_abi::proto::oak::log::Level { } } -/// Default name for predefined Node configuration that corresponds to a logging -/// pseudo-Node. -pub const DEFAULT_CONFIG_NAME: &str = "log"; - /// Initialize Node-wide default logging. /// /// Uses the default level (`Debug`) and the default pre-defined name @@ -73,7 +69,7 @@ pub const DEFAULT_CONFIG_NAME: &str = "log"; /// /// Panics if a logger has already been set. pub fn init_default() { - init(Level::Debug, DEFAULT_CONFIG_NAME).unwrap(); + init(Level::Debug).unwrap(); } /// Initialize Node-wide logging via a channel to a logging pseudo-Node. @@ -84,10 +80,10 @@ pub fn init_default() { /// # Errors /// /// An error is returned if a logger has already been set. -pub fn init(level: Level, config: &str) -> Result<(), SetLoggerError> { +pub fn init(level: Level) -> Result<(), SetLoggerError> { // Create a channel and pass the read half to a fresh logging Node. let (write_handle, read_handle) = crate::channel_create().expect("could not create channel"); - crate::node_create(config, "oak_main", read_handle).expect("could not create node"); + crate::node_create(&crate::node_config::log(), read_handle).expect("could not create node"); crate::channel_close(read_handle.handle).expect("could not close channel"); log::set_boxed_logger(Box::new(OakChannelLogger { diff --git a/sdk/rust/oak/src/node_config.rs b/sdk/rust/oak/src/node_config.rs new file mode 100644 index 00000000000..f01694c8bcf --- /dev/null +++ b/sdk/rust/oak/src/node_config.rs @@ -0,0 +1,57 @@ +// +// Copyright 2020 The Project Oak Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +//! TODO + +use oak_abi::proto::oak::application::{ + node_configuration::ConfigType, GrpcClientConfiguration, GrpcServerConfiguration, + LogConfiguration, NodeConfiguration, WebAssemblyConfiguration, +}; + +pub fn grpc_client(address: &str) -> NodeConfiguration { + NodeConfiguration { + name: "grpc_client".to_string(), + config_type: Some(ConfigType::GrpcClientConfig(GrpcClientConfiguration { + address: address.to_string(), + })), + } +} + +pub fn grpc_server(address: &str) -> NodeConfiguration { + NodeConfiguration { + name: "grpc_server".to_string(), + config_type: Some(ConfigType::GrpcServerConfig(GrpcServerConfiguration { + address: address.to_string(), + })), + } +} + +pub fn wasm(module_name: &str, entrypoint_name: &str) -> NodeConfiguration { + NodeConfiguration { + name: format!("wasm.{}.{}", module_name, entrypoint_name), + config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration { + wasm_module_name: module_name.to_string(), + wasm_entrypoint_name: entrypoint_name.to_string(), + })), + } +} + +pub fn log() -> NodeConfiguration { + NodeConfiguration { + name: "log".to_string(), + config_type: Some(ConfigType::LogConfig(LogConfiguration {})), + } +} diff --git a/sdk/rust/oak/src/roughtime/mod.rs b/sdk/rust/oak/src/roughtime/mod.rs index 3ef468e8f07..466d7d45da4 100644 --- a/sdk/rust/oak/src/roughtime/mod.rs +++ b/sdk/rust/oak/src/roughtime/mod.rs @@ -20,10 +20,9 @@ use crate::{ grpc, proto::oak::roughtime::{RoughTimeRequest, RoughtimeServiceClient}, }; - -/// Default name for predefined Node config that corresponds to a Roughtime -/// pseudo-Node. -pub const DEFAULT_CONFIG_NAME: &str = "roughtime"; +use oak_abi::proto::oak::application::{ + node_configuration::ConfigType, NodeConfiguration, RoughtimeClientConfiguration, +}; /// Local representation of the connection to an external Roughtime service. pub struct Roughtime { @@ -31,16 +30,13 @@ pub struct Roughtime { } impl Roughtime { - /// Create a default `Roughtime` instance assuming the default pre-defined - /// name (`"roughtime"`) identifying Roughtime pseudo-Node config. - pub fn default() -> Option { - Roughtime::new(DEFAULT_CONFIG_NAME) - } - - /// Create a `Roughtime` instance using the given name identifying Roughtime - /// pseudo-Node configuration. - pub fn new(config: &str) -> Option { - crate::grpc::client::Client::new(config, "oak_main").map(|client| Roughtime { + /// Creates a [`Roughtime`] instance using the given configuration. + pub fn new(config: &RoughtimeClientConfiguration) -> Option { + let config = NodeConfiguration { + name: "roughtime".to_string(), + config_type: Some(ConfigType::RoughtimeClientConfig(config.clone())), + }; + crate::grpc::client::Client::new(&config).map(|client| Roughtime { client: RoughtimeServiceClient(client), }) } diff --git a/sdk/rust/oak/src/storage/mod.rs b/sdk/rust/oak/src/storage/mod.rs index 1cd96e3a981..c43cd59f349 100644 --- a/sdk/rust/oak/src/storage/mod.rs +++ b/sdk/rust/oak/src/storage/mod.rs @@ -23,10 +23,9 @@ use crate::{ StorageItem, StorageServiceClient, }, }; - -/// Default name for predefined Node config that corresponds to a storage -/// pseudo-Node. -pub const DEFAULT_CONFIG_NAME: &str = "storage"; +use oak_abi::proto::oak::application::{ + node_configuration::ConfigType, NodeConfiguration, StorageProxyConfiguration, +}; /// Local representation of the connection to an external storage service. pub struct Storage { @@ -34,16 +33,13 @@ pub struct Storage { } impl Storage { - /// Create a default `Storage` instance assuming the default pre-defined - /// name (`"storage"`) identifying storage Node config. - pub fn default() -> Option { - Storage::new(DEFAULT_CONFIG_NAME) - } - - /// Create a `Storage` instance using the given name identifying storage - /// Node configuration. - pub fn new(config: &str) -> Option { - crate::grpc::client::Client::new(config, "oak_main").map(|client| Storage { + /// Creates a [`Storage`] instance using the given configuration. + pub fn new(config: &StorageProxyConfiguration) -> Option { + crate::grpc::client::Client::new(&NodeConfiguration { + name: "storage".to_string(), + config_type: Some(ConfigType::StorageConfig(config.clone())), + }) + .map(|client| Storage { client: StorageServiceClient(client), }) } diff --git a/sdk/rust/oak/src/stubs.rs b/sdk/rust/oak/src/stubs.rs index d6db925ee3c..cd9f287bd82 100644 --- a/sdk/rust/oak/src/stubs.rs +++ b/sdk/rust/oak/src/stubs.rs @@ -72,13 +72,7 @@ pub extern "C" fn channel_close(_handle: u64) -> u32 { panic!("stub function invoked!"); } #[no_mangle] -pub extern "C" fn node_create( - _config_buf: *const u8, - _config_len: usize, - _entrypoint_buf: *const u8, - _entrypoint_len: usize, - _handle: u64, -) -> u32 { +pub extern "C" fn node_create(_config_buf: *const u8, _config_len: usize, _handle: u64) -> u32 { panic!("stub function invoked!"); } #[no_mangle] diff --git a/sdk/rust/oak_tests/src/lib.rs b/sdk/rust/oak_tests/src/lib.rs index a837c2f76d9..54f404ab7f0 100644 --- a/sdk/rust/oak_tests/src/lib.rs +++ b/sdk/rust/oak_tests/src/lib.rs @@ -17,12 +17,13 @@ //! Test utilities to help with unit testing of Oak SDK code. use log::info; - +use oak_abi::proto::oak::application::{ + node_configuration::ConfigType, NodeConfiguration, WebAssemblyConfiguration, +}; +use oak_runtime::runtime::TEST_NODE_ID; use prost::Message; use std::{collections::HashMap, process::Command, sync::Arc}; -use oak_runtime::runtime::TEST_NODE_ID; - // TODO(#544): re-enable unit tests of SDK functionality /// Uses cargo to compile a Rust manifest to Wasm bytes. @@ -48,21 +49,20 @@ pub fn compile_rust_wasm(cargo_path: &str, module_name: &str) -> std::io::Result std::fs::read(path) } -const DEFAULT_LOG_CONFIG_NAME: &str = "log"; const DEFAULT_ENTRYPOINT_NAME: &str = "oak_main"; const DEFAULT_MODULE_MANIFEST: &str = "Cargo.toml"; const MODULE_WASM_SUFFIX: &str = ".wasm"; -/// Convenience helper to build and run a single-Node Application with the -/// given module name, using the default name "oak_main" for its entrypoint. +/// Convenience helper to build and run a single-Node Application with the given module name, using +/// the default name "oak_main" for its entrypoint. pub fn run_single_module_default( module_config_name: &str, ) -> Result<(Arc, oak_runtime::Handle), oak::OakStatus> { run_single_module(module_config_name, DEFAULT_ENTRYPOINT_NAME) } -/// Convenience helper to build and run a single-Node application with the -/// given module name, using the provided entrypoint name. +/// Convenience helper to build and run a single-Node application with the given module name, using +/// the provided entrypoint name. pub fn run_single_module( module_config_name: &str, entrypoint_name: &str, @@ -81,9 +81,13 @@ pub fn run_single_module( let configuration = oak_runtime::application_configuration( wasm, - DEFAULT_LOG_CONFIG_NAME, - module_config_name, - entrypoint_name, + &NodeConfiguration { + name: "test".to_string(), + config_type: Some(ConfigType::WasmConfig(WebAssemblyConfiguration { + wasm_module_name: module_config_name.to_string(), + wasm_entrypoint_name: entrypoint_name.to_string(), + })), + }, ); oak_runtime::configure_and_run(configuration)