diff --git a/Cargo.lock b/Cargo.lock index ca1e7ffa..b43170c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,6 +82,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "bincode" version = "1.3.1" @@ -99,10 +105,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "blake3" -version = "0.3.5" +name = "blake2b_simd" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f88a20f7dc23e3896bcbd85add056543c87215de721468b90e0c85d5a9f365" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake3" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce4f9586c9a3151c4b49b19e82ba163dd073614dd057e53c969e1a4db5b52720" dependencies = [ "arrayref", "arrayvec", @@ -133,9 +150,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "clap" -version = "2.33.1" +version = "2.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +checksum = "10040cdf04294b565d9e0319955430099ec3813a64c952b86a41200ad714ae48" dependencies = [ "ansi_term", "atty", @@ -313,6 +330,27 @@ dependencies = [ "generic-array 0.14.3", ] +[[package]] +name = "dirs-next" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "effector" version = "0.1.0" @@ -431,6 +469,19 @@ dependencies = [ "serde_json", ] +[[package]] +name = "fce-repl" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "fluence-app-service", + "rustop", + "rustyline", + "serde_json", + "uuid", +] + [[package]] name = "fce-wit-generator" version = "0.1.1" @@ -466,7 +517,7 @@ dependencies = [ [[package]] name = "fluence" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#7018a64c29278a673e543867e691f74b3d652288" +source = "git+https://github.com/fluencelabs/rust-sdk#8418111074644a1bc197315060facd34778099de" dependencies = [ "fluence-sdk-macro", "fluence-sdk-main", @@ -476,16 +527,10 @@ dependencies = [ name = "fluence-app-service" version = "0.1.0" dependencies = [ - "cmd_lib", "fluence-faas", "log", - "serde", - "serde_derive", "serde_json", "toml", - "wasmer-runtime-core-fl", - "wasmer-runtime-fl", - "wasmer-wasi-fl", ] [[package]] @@ -507,7 +552,7 @@ dependencies = [ [[package]] name = "fluence-sdk-macro" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#7018a64c29278a673e543867e691f74b3d652288" +source = "git+https://github.com/fluencelabs/rust-sdk#8418111074644a1bc197315060facd34778099de" dependencies = [ "fluence-sdk-wit 0.2.0 (git+https://github.com/fluencelabs/rust-sdk)", ] @@ -515,7 +560,7 @@ dependencies = [ [[package]] name = "fluence-sdk-main" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#7018a64c29278a673e543867e691f74b3d652288" +source = "git+https://github.com/fluencelabs/rust-sdk#8418111074644a1bc197315060facd34778099de" dependencies = [ "log", ] @@ -523,7 +568,7 @@ dependencies = [ [[package]] name = "fluence-sdk-wit" version = "0.2.0" -source = "git+https://github.com/fluencelabs/rust-sdk#7018a64c29278a673e543867e691f74b3d652288" +source = "git+https://github.com/fluencelabs/rust-sdk#8418111074644a1bc197315060facd34778099de" dependencies = [ "proc-macro2", "quote", @@ -715,7 +760,7 @@ version = "0.1.0" dependencies = [ "anyhow", "env_logger", - "fluence-faas", + "fluence-app-service", "log", ] @@ -752,9 +797,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.73" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7d4bd64732af4bf3a67f367c27df8520ad7e230c5817b8ff485864d80242b9" +checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" [[package]] name = "lock_api" @@ -836,6 +881,19 @@ dependencies = [ "void", ] +[[package]] +name = "nix" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "void", +] + [[package]] name = "nom" version = "5.1.2" @@ -1051,6 +1109,17 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "redox_users" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" +dependencies = [ + "getrandom", + "redox_syscall", + "rust-argon2", +] + [[package]] name = "regex" version = "1.3.9" @@ -1069,6 +1138,18 @@ version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" +[[package]] +name = "rust-argon2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" +dependencies = [ + "base64", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils", +] + [[package]] name = "rustc-demangle" version = "0.1.16" @@ -1084,6 +1165,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustop" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccb57833f46c9d679d68f383e81b9a74b82aa8d6f8a9c9aaae7fc29476bc3274" + +[[package]] +name = "rustyline" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" +dependencies = [ + "cfg-if", + "dirs-next", + "libc", + "log", + "memchr", + "nix 0.17.0", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + [[package]] name = "ryu" version = "1.0.5" @@ -1193,9 +1299,9 @@ checksum = "502d53007c02d7605a05df1c1a73ee436952781653da5d0bf57ad608f66932c1" [[package]] name = "syn" -version = "1.0.36" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" dependencies = [ "proc-macro2", "quote", @@ -1341,6 +1447,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "uuid" version = "0.8.1" @@ -1434,7 +1546,7 @@ dependencies = [ "cranelift-entity", "cranelift-native", "libc", - "nix", + "nix 0.15.0", "rayon", "serde", "serde-bench", @@ -1503,7 +1615,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "nix", + "nix 0.15.0", "page_size", "parking_lot", "rustc_version", @@ -1532,7 +1644,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "nix", + "nix 0.15.0", "page_size", "parking_lot", "rustc_version", diff --git a/Cargo.toml b/Cargo.toml index bd13de23..3d8bc2ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "fluence-app-service", "fluence-faas", "tools/cli", + "tools/repl", ] [profile.release] diff --git a/engine/src/engine.rs b/engine/src/engine.rs index a41df11e..5a79a214 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -67,16 +67,16 @@ impl FCE { /// Load a new module inside FCE. pub fn load_module>( &mut self, - module_name: S, + name: S, wasm_bytes: &[u8], config: FCEModuleConfig, ) -> Result<()> { - self.load_module_(module_name.into(), wasm_bytes, config) + self.load_module_(name.into(), wasm_bytes, config) } pub fn load_module_( &mut self, - module_name: String, + name: String, wasm_bytes: &[u8], config: FCEModuleConfig, ) -> Result<()> { @@ -84,7 +84,7 @@ impl FCE { let module = FCEModule::new(&wasm_bytes, config, &self.modules)?; - match self.modules.entry(module_name) { + match self.modules.entry(name) { Entry::Vacant(entry) => { entry.insert(module); Ok(()) @@ -94,8 +94,8 @@ impl FCE { } /// Unload previously loaded module. - pub fn unload_module>(&mut self, module_name: S) -> Result<()> { - self.unload_module_(module_name.as_ref()) + pub fn unload_module>(&mut self, name: S) -> Result<()> { + self.unload_module_(name.as_ref()) } pub fn unload_module_(&mut self, module_name: &str) -> Result<()> { diff --git a/examples/greeting/src/main.rs b/examples/greeting/src/main.rs index 0fd70fc0..5ade146b 100644 --- a/examples/greeting/src/main.rs +++ b/examples/greeting/src/main.rs @@ -28,7 +28,7 @@ fn main() -> Result<(), anyhow::Error> { greeting_node.get_interface() ); - let result = greeting_node.call_module( + let result = greeting_node.call( "greeting.wasm", "greeting", &[IValue::String("Fluence".to_string()), IValue::I32(1)], diff --git a/examples/ipfs_node/Cargo.toml b/examples/ipfs_node/Cargo.toml index 6dc25eb0..a35f3522 100644 --- a/examples/ipfs_node/Cargo.toml +++ b/examples/ipfs_node/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Fluence Labs"] edition = "2018" [dependencies] -fluence-faas = { path = "../../fluence-faas" } +fluence-app-service = { path = "../../fluence-app-service" } anyhow = "1.0.31" log = "0.4.11" env_logger = "0.7.1" diff --git a/examples/ipfs_node/Config.toml b/examples/ipfs_node/Config.toml index 8d6aa637..9009306c 100644 --- a/examples/ipfs_node/Config.toml +++ b/examples/ipfs_node/Config.toml @@ -1,24 +1,18 @@ -core_modules_dir = "wasm/artifacts/wasm_modules" +modules_dir = "wasm/artifacts/wasm_modules" +service_base_dir = "/Users/tmp" -[[core_module]] +[[module]] name = "ipfs_node.wasm" mem_pages_count = 100 logger_enabled = true - [core_module.imports] - mysql = "/usr/bin/mysql" + [module.imports] ipfs = "/usr/local/bin/ipfs" - [core_module.wasi] + [module.wasi] envs = ["IPFS_ADDR=/dns4/relay02.fluence.dev/tcp/15001", "timeout=1s"] - preopened_files = ["./wasm/artifacts"] - mapped_dirs = { "tmp" = "./wasm/artifacts" } -[rpc_module] +[[module]] + name = "zipfs_rpc.wasm" mem_pages_count = 100 logger_enabled = true - - [rpc_module.wasi] - envs = [] - preopened_files = ["./wasm/artifacts"] - mapped_dirs = { "tmp" = "./wasm/artifacts" } diff --git a/examples/ipfs_node/src/main.rs b/examples/ipfs_node/src/main.rs index 0e28745e..e03dcc39 100644 --- a/examples/ipfs_node/src/main.rs +++ b/examples/ipfs_node/src/main.rs @@ -14,27 +14,23 @@ * limitations under the License. */ -use fluence_faas::FluenceFaaS; -use fluence_faas::IValue; +use fluence_app_service::AppService; +use fluence_app_service::IValue; use std::path::PathBuf; -use anyhow::Context; const IPFS_MODULES_CONFIG_PATH: &str = "Config.toml"; -const IPFS_RPC: &str = "wasm/artifacts/ipfs_rpc.wasm"; fn main() -> Result<(), anyhow::Error> { env_logger::init(); - let ipfs_rpc = std::fs::read(IPFS_RPC).with_context(|| format!("{} wasn't found", IPFS_RPC))?; - - let mut ipfs_node = FluenceFaaS::new(PathBuf::from(IPFS_MODULES_CONFIG_PATH))?; + let mut ipfs_node = AppService::with_raw_config(); println!("ipfs node interface is\n{}", ipfs_node.get_interface()); - let node_address = ipfs_node.call_module("ipfs_node.wasm", "get_address", &[])?; + let node_address = ipfs_node.call("ipfs_node.wasm", "get_address", &[])?; println!("ipfs node address is:\n{:?}", node_address); - let result = ipfs_node.call_code( - &ipfs_rpc, + let result = ipfs_node.call( + "ipfs_rpc.wasm", "get", &[IValue::String( "QmXdC36pX1B1sdHdbri859vMYctQjAhvTmkWyG9xzhShxb".to_string(), diff --git a/examples/ipfs_node/wasm/artifacts/ipfs_rpc.wasm b/examples/ipfs_node/wasm/artifacts/wasm_modules/zipfs_rpc.wasm similarity index 100% rename from examples/ipfs_node/wasm/artifacts/ipfs_rpc.wasm rename to examples/ipfs_node/wasm/artifacts/wasm_modules/zipfs_rpc.wasm diff --git a/examples/records/src/main.rs b/examples/records/src/main.rs index d4949c31..59510eb5 100644 --- a/examples/records/src/main.rs +++ b/examples/records/src/main.rs @@ -26,7 +26,7 @@ fn main() -> Result<(), anyhow::Error> { let mut records_test = FluenceFaaS::new(PathBuf::from(RECORDS_MODULES_CONFIG_PATH))?; println!("ipfs node interface is\n{}", records_test.get_interface()); - let result = records_test.call_module("pure.wasm", "invoke", &[])?; + let result = records_test.call("pure.wasm", "invoke", &[])?; println!("execution result {:?}", result); Ok(()) diff --git a/fluence-app-service/Cargo.toml b/fluence-app-service/Cargo.toml index c5dd6303..79577a29 100644 --- a/fluence-app-service/Cargo.toml +++ b/fluence-app-service/Cargo.toml @@ -7,14 +7,9 @@ edition = "2018" [dependencies] fluence-faas = { path = "../fluence-faas" } -wasmer-runtime = { package = "wasmer-runtime-fl", version = "0.17.0" } -# dynamicfunc-fat-closures allows using state inside DynamicFunc -wasmer-core = { package = "wasmer-runtime-core-fl", version = "0.17.0", features = ["dynamicfunc-fat-closures"] } -wasmer-wasi = { package = "wasmer-wasi-fl", version = "0.17.0" } - toml = "0.5.6" -serde = { version = "1.0.111", features = ["derive"] } serde_json = "1.0.53" -serde_derive = "1.0.111" -cmd_lib = "0.7.8" log = "0.4.8" + +[features] +raw-module-api = ["fluence-faas/raw-module-api"] diff --git a/fluence-app-service/src/errors.rs b/fluence-app-service/src/errors.rs index 9bb4c36d..6ca8d0dd 100644 --- a/fluence-app-service/src/errors.rs +++ b/fluence-app-service/src/errors.rs @@ -22,7 +22,7 @@ use std::error::Error; #[derive(Debug)] pub enum AppServiceError { /// An error related to config parsing. - InvalidArguments(String), + InvalidConfig(String), /// Various errors related to file i/o. IOError(String), @@ -36,7 +36,7 @@ impl Error for AppServiceError {} impl std::fmt::Display for AppServiceError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { match self { - AppServiceError::InvalidArguments(err_msg) => write!(f, "{}", err_msg), + AppServiceError::InvalidConfig(err_msg) => write!(f, "{}", err_msg), AppServiceError::IOError(err_msg) => write!(f, "{}", err_msg), AppServiceError::FaaSError(err) => write!(f, "{}", err), } @@ -57,12 +57,12 @@ impl From for AppServiceError { impl From for AppServiceError { fn from(err: toml::de::Error) -> Self { - AppServiceError::InvalidArguments(format!("{}", err)) + AppServiceError::InvalidConfig(format!("{}", err)) } } impl From for AppServiceError { - fn from(inf: std::convert::Infallible) -> Self { - match inf {} + fn from(_: std::convert::Infallible) -> Self { + unreachable!() } } diff --git a/fluence-app-service/src/service.rs b/fluence-app-service/src/service.rs index bbcb1e02..f913aba8 100644 --- a/fluence-app-service/src/service.rs +++ b/fluence-app-service/src/service.rs @@ -22,6 +22,7 @@ use fluence_faas::FluenceFaaS; use fluence_faas::ModulesConfig; use std::convert::TryInto; +use std::path::PathBuf; const SERVICE_ID_ENV_NAME: &str = "service_id"; const SERVICE_LOCAL_DIR_NAME: &str = "local"; @@ -35,7 +36,7 @@ pub struct AppService { } impl AppService { - /// Creates Service with given modules and service id. + /// Create Service with given modules and service id. pub fn new(modules: I, config: C, service_id: S) -> Result where I: IntoIterator, @@ -45,7 +46,7 @@ impl AppService { { let config: ModulesConfig = config.try_into()?; let service_id = service_id.as_ref(); - let config = Self::set_env_and_dirs(config, service_id)?; + let config = Self::set_env_and_dirs(config, service_id, None)?; let modules = modules.into_iter().collect(); let faas = FluenceFaaS::with_module_names(&modules, config)?; @@ -53,9 +54,33 @@ impl AppService { Ok(Self { faas }) } + /// Create Service with given raw config, service id and service base dir. + pub fn with_raw_config( + config: P, + service_id: SI, + service_base_dir: Option<&str>, + ) -> Result + where + P: Into, + SI: AsRef, + { + let service_id = service_id.as_ref(); + let service_base_dir = service_base_dir; + + let config_content = std::fs::read(config.into())?; + let config: crate::RawModulesConfig = toml::from_slice(&config_content)?; + + let config = config.try_into()?; + let config = Self::set_env_and_dirs(config, service_id, service_base_dir)?; + + let faas = FluenceFaaS::with_raw_config(config)?; + + Ok(Self { faas }) + } + /// Call a specified function of loaded module by its name. // TODO: replace serde_json::Value with Vec? - pub fn call_module, FN: AsRef>( + pub fn call, FN: AsRef>( &mut self, module_name: MN, func_name: FN, @@ -64,7 +89,7 @@ impl AppService { let arguments = Self::json_to_ivalue(arguments)?; self.faas - .call_module(module_name, func_name, &arguments) + .call(module_name, func_name, &arguments) .map_err(Into::into) } @@ -78,11 +103,15 @@ impl AppService { /// - service_base_dir/service_id/SERVICE_LOCAL_DIR_NAME /// - service_base_dir/service_id/SERVICE_TMP_DIR_NAME /// 2. adding service_id to environment variables - fn set_env_and_dirs(mut config: ModulesConfig, service_id: &str) -> Result { - let base_dir = match config.service_base_dir { - Some(ref base_dir) => base_dir, - // TODO: refactor it later - None => { + fn set_env_and_dirs( + mut config: ModulesConfig, + service_id: &str, + service_base_dir: Option<&str>, + ) -> Result { + let base_dir = match (&config.service_base_dir, service_base_dir) { + (_, Some(base_dir)) => base_dir, + (Some(ref base_dir), None) => base_dir, + _ => { return Err(AppServiceError::IOError(String::from( "service_base_dir should be specified", ))) @@ -130,7 +159,7 @@ impl AppService { let is_empty_obj = arguments.as_object().map_or(false, |m| m.is_empty()); let arguments = if !is_null && !is_empty_arr && !is_empty_obj { Some(fluence_faas::to_interface_value(&arguments).map_err(|e| { - AppServiceError::InvalidArguments(format!( + AppServiceError::InvalidConfig(format!( "can't parse arguments as array of interface types: {}", e )) @@ -143,10 +172,29 @@ impl AppService { Some(IValue::Record(arguments)) => Ok(arguments.into_vec()), // Convert null, [] and {} into vec![] None => Ok(vec![]), - other => Err(AppServiceError::InvalidArguments(format!( + other => Err(AppServiceError::InvalidConfig(format!( "expected array of interface values: got {:?}", other ))), } } } + +// This API is intended for testing purposes (mostly in FCE REPL) +#[cfg(feature = "raw-module-api")] +impl AppService { + pub fn load_module(&mut self, name: S, wasm_bytes: &[u8], config: Option) -> Result<()> + where + S: Into, + C: TryInto, + fluence_faas::FaaSError: From, + { + self.faas + .load_module(name, &wasm_bytes, config) + .map_err(Into::into) + } + + pub fn unload_module>(&mut self, module_name: S) -> Result<()> { + self.faas.unload_module(module_name).map_err(Into::into) + } +} diff --git a/fluence-faas/Cargo.toml b/fluence-faas/Cargo.toml index 1aef0957..1756c045 100644 --- a/fluence-faas/Cargo.toml +++ b/fluence-faas/Cargo.toml @@ -18,3 +18,6 @@ serde_json = "1.0.53" serde_derive = "1.0.111" cmd_lib = "0.7.8" log = "0.4.8" + +[features] +raw-module-api = [] diff --git a/fluence-faas/src/errors.rs b/fluence-faas/src/errors.rs index d75cef0d..754a8db1 100644 --- a/fluence-faas/src/errors.rs +++ b/fluence-faas/src/errors.rs @@ -62,7 +62,7 @@ impl From for FaaSError { } impl From for FaaSError { - fn from(inf: std::convert::Infallible) -> Self { - match inf {} + fn from(_: std::convert::Infallible) -> Self { + unreachable!() } } diff --git a/fluence-faas/src/faas.rs b/fluence-faas/src/faas.rs index 587c1546..fbfe7695 100644 --- a/fluence-faas/src/faas.rs +++ b/fluence-faas/src/faas.rs @@ -15,19 +15,18 @@ */ use crate::misc::ModulesConfig; +use crate::faas_interface::FaaSFunctionSignature; +use crate::faas_interface::FaaSInterface; +use crate::FaaSError; use crate::Result; - -use super::faas_interface::FaaSInterface; -use super::FaaSError; -use super::IValue; +use crate::IValue; use fce::FCE; use std::convert::TryInto; +use std::collections::HashSet; use std::fs; use std::path::PathBuf; -use crate::faas_interface::FaaSFunctionSignature; -use std::collections::HashSet; // TODO: remove and use mutex instead unsafe impl Send for FluenceFaaS {} @@ -86,13 +85,15 @@ impl FluenceFaaS { /// Creates FaaS from config deserialized from TOML. pub fn with_raw_config(config: C) -> Result where - C: TryInto, + C: TryInto, + FaaSError: From, { let config = config.try_into()?; let modules = config.modules_dir.as_ref().map_or(Ok(vec![]), |dir| { Self::load_modules(dir, ModulesLoadStrategy::All) })?; - Self::with_modules(modules, config) + + Self::with_modules::<_, ModulesConfig>(modules, config) } /// Creates FaaS with given modules. @@ -110,7 +111,6 @@ impl FluenceFaaS { module_config @ Some(_) => module_config, None => config.default_modules_config.clone(), }; - let fce_module_config = crate::misc::make_fce_config(module_config)?; fce.load_module(name.clone(), &bytes, fce_module_config)?; } @@ -178,7 +178,7 @@ impl FluenceFaaS { } /// Call a specified function of loaded on a startup module by its name. - pub fn call_module, FN: AsRef>( + pub fn call, FN: AsRef>( &mut self, module_name: MN, func_name: FN, @@ -214,3 +214,25 @@ impl FluenceFaaS { FaaSInterface { modules } } } + +// This API is intended for testing purposes (mostly in FCE REPL) +#[cfg(feature = "raw-module-api")] +impl FluenceFaaS { + pub fn load_module(&mut self, name: S, wasm_bytes: &[u8], config: Option) -> Result<()> + where + S: Into, + C: TryInto, + FaaSError: From, + { + let config = config.map(|c| c.try_into()).transpose()?; + + let fce_module_config = crate::misc::make_fce_config(config)?; + self.fce + .load_module(name, &wasm_bytes, fce_module_config) + .map_err(Into::into) + } + + pub fn unload_module>(&mut self, module_name: S) -> Result<()> { + self.fce.unload_module(module_name).map_err(Into::into) + } +} diff --git a/fluence-faas/src/misc/config.rs b/fluence-faas/src/misc/config.rs index 0b79777a..7f1c980b 100644 --- a/fluence-faas/src/misc/config.rs +++ b/fluence-faas/src/misc/config.rs @@ -18,7 +18,6 @@ use crate::FaaSError; use crate::Result; use serde_derive::{Serialize, Deserialize}; -use toml::from_slice; use std::collections::HashMap; use std::convert::TryInto; @@ -70,7 +69,7 @@ impl TryInto for RawModulesConfig { type Error = FaaSError; fn try_into(self) -> Result { - from_raw_config(self) + from_raw_modules_config(self) } } @@ -83,6 +82,14 @@ pub struct RawModuleConfig { pub wasi: Option, } +impl TryInto for RawModuleConfig { + type Error = FaaSError; + + fn try_into(self) -> Result { + from_raw_module_config(self).map(|(_, module_config)| module_config) + } +} + #[derive(Deserialize, Serialize, Debug, Clone, Default)] pub struct RawDefaultModuleConfig { pub mem_pages_count: Option, @@ -209,7 +216,7 @@ pub struct WASIConfig { } /// Prepare config after parsing it from TOML. -fn from_raw_config(config: RawModulesConfig) -> Result { +fn from_raw_modules_config(config: RawModulesConfig) -> Result { let service_base_dir = config.service_base_dir; let modules_config = config .module @@ -233,7 +240,7 @@ fn from_raw_config(config: RawModulesConfig) -> Result { /// Parse config from TOML. pub(crate) fn load_config(config_file_path: std::path::PathBuf) -> Result { let file_content = std::fs::read(config_file_path)?; - Ok(from_slice(&file_content)?) + Ok(toml::from_slice(&file_content)?) } fn from_raw_module_config(config: RawModuleConfig) -> Result<(String, ModuleConfig)> { diff --git a/tools/repl/Cargo.toml b/tools/repl/Cargo.toml new file mode 100644 index 00000000..f0c67506 --- /dev/null +++ b/tools/repl/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "fce-repl" +description = "Fluence FCE REPL intended for testing purposes" +version = "0.1.0" +authors = ["Fluence Labs"] +repository = "https://github.com/fluencelabs/fce/tools/repl" +license = "Apache-2.0" +edition = "2018" + +[[bin]] +name = "fce-repl" +path = "src/main.rs" + +[dependencies] +fluence-app-service = { path = "../../fluence-app-service", features = ["raw-module-api"] } + +anyhow = "1.0.31" +clap = "2.33.1" +serde_json = "1.0.57" + +rustyline = "6.1.2" +rustop = "1.1.0" +uuid = { version = "0.8.1", features = ["v4"] } diff --git a/tools/repl/src/main.rs b/tools/repl/src/main.rs new file mode 100644 index 00000000..94875069 --- /dev/null +++ b/tools/repl/src/main.rs @@ -0,0 +1,174 @@ +/* + * Copyright 2020 Fluence Labs Limited + * + * 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. + */ + +#![deny( + nonstandard_style, + unused_imports, + unused_mut, + unused_variables, + unused_unsafe, + unreachable_patterns +)] +#![warn(rust_2018_idioms)] + +/// Command-line tool intended to test Fluence FaaS. +use std::fs; + +macro_rules! next_argument { + ($arg_name:ident, $args:ident, $error_msg:expr) => { + let $arg_name = if let Some($arg_name) = $args.next() { + $arg_name + } else { + println!($error_msg); + continue; + }; + }; +} + +fn main() -> Result<(), anyhow::Error> { + let (args, _) = rustop::opts! { + synopsis "Fluence Application service REPL"; + param config_file_path: Option, desc: "Path to a service config"; + } + .parse_or_exit(); + + println!("Welcome to the Fluence FaaS REPL:"); + let mut app_service = create_service_from_config(args.config_file_path)?; + + let mut rl = rustyline::Editor::<()>::new(); + loop { + let readline = rl.readline(">> "); + let readline = match readline { + Ok(readline) => readline, + Err(e) => { + println!("a error occurred: {}", e); + break; + } + }; + + let mut args = readline.split_whitespace(); + match args.next() { + Some("new") => { + app_service = match create_service_from_config(args.next()) { + Ok(service) => service, + Err(e) => { + println!("failed to create a new application service: {}", e); + app_service + } + }; + } + Some("load") => { + next_argument!(module_name, args, "Module name should be specified"); + next_argument!(module_path, args, "Module path should be specified"); + + let wasm_bytes = fs::read(module_path); + if let Err(e) = wasm_bytes { + println!("failed to read wasm module: {}", e); + continue; + } + + let result_msg = match app_service + .load_module::( + module_name.into(), + &wasm_bytes.unwrap(), + None, + ) { + Ok(_) => "module successfully loaded into App service".to_string(), + Err(e) => format!("module loaded failed with: {:?}", e), + }; + println!("{}", result_msg); + } + Some("unload") => { + next_argument!(module_name, args, "Module name should be specified"); + + let result_msg = match app_service.unload_module(module_name) { + Ok(_) => "module successfully unloaded from App service".to_string(), + Err(e) => format!("module unloaded failed with: {:?}", e), + }; + println!("{}", result_msg); + } + Some("call") => { + next_argument!(module_name, args, "Module name should be specified"); + next_argument!(func_name, args, "Function name should be specified"); + + let module_arg: String = args.collect(); + let module_arg: serde_json::Value = match serde_json::from_str(&module_arg) { + Ok(module_arg) => module_arg, + Err(e) => { + println!("incorrect arguments {}", e); + continue; + } + }; + + let result = match app_service.call(module_name, func_name, module_arg) { + Ok(result) => format!("result: {:?}", result), + Err(e) => format!("execution failed with {:?}", e), + }; + println!("{}", result); + } + Some("interface") => { + let interface = app_service.get_interface(); + println!("application service interface: {}", interface); + } + Some("h") | Some("help") | None => { + println!( + "Enter:\n\ + new [config_path] - to create a new AppService (old will be removed) + load - to load a new Wasm module into App service\n\ + unload - to unload Wasm module from AppService\n\ + call [args] - to call function with given name on module with given module_name\n\ + interface - to print public interface of current AppService\n\ + h/help - to print this message\n\ + e/exit/q/quit - to exit" + ); + } + Some("e") | Some("exit") | Some("q") | Some("quit") => break, + _ => { + println!("unsupported command"); + } + } + } + + Ok(()) +} + +fn create_service_from_config>( + config_file_path: Option, +) -> Result { + let tmp_path: String = std::env::temp_dir().to_string_lossy().into(); + let service_id = uuid::Uuid::new_v4().to_string(); + + let app_service = match config_file_path { + Some(config_file_path) => { + let config_file_path = config_file_path.into(); + fluence_app_service::AppService::with_raw_config( + config_file_path, + &service_id, + Some(&tmp_path), + ) + } + None => { + let mut config: fluence_app_service::RawModulesConfig = <_>::default(); + config.service_base_dir = Some(tmp_path); + + fluence_app_service::AppService::new(std::iter::empty(), config, &service_id) + } + }?; + + println!("app service's created with service id = {}", service_id); + + Ok(app_service) +}