mirror of
https://github.com/fluencelabs/marine.git
synced 2024-12-12 06:45:32 +00:00
Introduce service (#13)
This commit is contained in:
parent
32ffc7c8a7
commit
c06dddd5ae
16
Cargo.lock
generated
16
Cargo.lock
generated
@ -472,6 +472,22 @@ dependencies = [
|
||||
"fluence-sdk-main",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
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]]
|
||||
name = "fluence-faas"
|
||||
version = "0.1.0"
|
||||
|
@ -13,6 +13,7 @@ members = [
|
||||
"examples/records/wasm/effector",
|
||||
"examples/records/wasm/pure",
|
||||
"examples/records/wasm/test-record",
|
||||
"fluence-app-service",
|
||||
"fluence-faas",
|
||||
"tools/cli",
|
||||
]
|
||||
|
20
fluence-app-service/Cargo.toml
Normal file
20
fluence-app-service/Cargo.toml
Normal file
@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "fluence-app-service"
|
||||
version = "0.1.0"
|
||||
authors = ["Fluence Labs"]
|
||||
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"
|
3
fluence-app-service/README.md
Normal file
3
fluence-app-service/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Fluence Application Service
|
||||
|
||||
Fluence Application Service intended to run various Wasm binaries. At now, it is in the heavily developing phase. For more details please see the IPFS [example](https://github.com/fluencelabs/fce/tree/master/examples/ipfs_node).
|
68
fluence-app-service/src/errors.rs
Normal file
68
fluence-app-service/src/errors.rs
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use fluence_faas::FaaSError;
|
||||
|
||||
use std::io::Error as IOError;
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AppServiceError {
|
||||
/// An error related to config parsing.
|
||||
InvalidArguments(String),
|
||||
|
||||
/// Various errors related to file i/o.
|
||||
IOError(String),
|
||||
|
||||
/// FaaS errors.
|
||||
FaaSError(FaaSError),
|
||||
}
|
||||
|
||||
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::IOError(err_msg) => write!(f, "{}", err_msg),
|
||||
AppServiceError::FaaSError(err) => write!(f, "{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IOError> for AppServiceError {
|
||||
fn from(err: IOError) -> Self {
|
||||
AppServiceError::IOError(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FaaSError> for AppServiceError {
|
||||
fn from(err: FaaSError) -> Self {
|
||||
AppServiceError::FaaSError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for AppServiceError {
|
||||
fn from(err: toml::de::Error) -> Self {
|
||||
AppServiceError::InvalidArguments(format!("{}", err))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::convert::Infallible> for AppServiceError {
|
||||
fn from(inf: std::convert::Infallible) -> Self {
|
||||
match inf {}
|
||||
}
|
||||
}
|
44
fluence-app-service/src/lib.rs
Normal file
44
fluence-app-service/src/lib.rs
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod errors;
|
||||
mod service;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, AppServiceError>;
|
||||
|
||||
pub use errors::AppServiceError;
|
||||
pub use service::AppService;
|
||||
|
||||
pub use fluence_faas::IValue;
|
||||
pub use fluence_faas::IType;
|
||||
pub use fluence_faas::FaaSInterface;
|
||||
pub use fluence_faas::RawModulesConfig;
|
||||
pub use fluence_faas::RawModuleConfig;
|
||||
pub use fluence_faas::ModulesConfig;
|
||||
pub use fluence_faas::ModuleConfig;
|
||||
pub use fluence_faas::WASIConfig;
|
||||
pub use fluence_faas::to_interface_value;
|
||||
pub use fluence_faas::from_interface_values;
|
152
fluence-app-service/src/service.rs
Normal file
152
fluence-app-service/src/service.rs
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
use crate::Result;
|
||||
use super::AppServiceError;
|
||||
use super::IValue;
|
||||
|
||||
use fluence_faas::FluenceFaaS;
|
||||
use fluence_faas::ModulesConfig;
|
||||
|
||||
use std::convert::TryInto;
|
||||
|
||||
const SERVICE_ID_ENV_NAME: &str = "service_id";
|
||||
const SERVICE_LOCAL_DIR_NAME: &str = "local";
|
||||
const SERVICE_TMP_DIR_NAME: &str = "tmp";
|
||||
|
||||
// TODO: remove and use mutex instead
|
||||
unsafe impl Send for AppService {}
|
||||
|
||||
pub struct AppService {
|
||||
faas: FluenceFaaS,
|
||||
}
|
||||
|
||||
impl AppService {
|
||||
/// Creates Service with given modules and service id.
|
||||
pub fn new<I, C, S>(modules: I, config: C, service_id: S) -> Result<Self>
|
||||
where
|
||||
I: IntoIterator<Item = String>,
|
||||
C: TryInto<ModulesConfig>,
|
||||
S: AsRef<str>,
|
||||
AppServiceError: From<C::Error>,
|
||||
{
|
||||
let config: ModulesConfig = config.try_into()?;
|
||||
let service_id = service_id.as_ref();
|
||||
let config = Self::set_env_and_dirs(config, service_id)?;
|
||||
|
||||
let modules = modules.into_iter().collect();
|
||||
let faas = FluenceFaaS::with_module_names(&modules, config)?;
|
||||
|
||||
Ok(Self { faas })
|
||||
}
|
||||
|
||||
/// Call a specified function of loaded module by its name.
|
||||
// TODO: replace serde_json::Value with Vec<u8>?
|
||||
pub fn call_module<MN: AsRef<str>, FN: AsRef<str>>(
|
||||
&mut self,
|
||||
module_name: MN,
|
||||
func_name: FN,
|
||||
arguments: serde_json::Value,
|
||||
) -> Result<Vec<IValue>> {
|
||||
let arguments = Self::json_to_ivalue(arguments)?;
|
||||
|
||||
self.faas
|
||||
.call_module(module_name, func_name, &arguments)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Return all export functions (name and signatures) of loaded modules.
|
||||
pub fn get_interface(&self) -> fluence_faas::FaaSInterface<'_> {
|
||||
self.faas.get_interface()
|
||||
}
|
||||
|
||||
/// Prepare service before starting by:
|
||||
/// 1. creating a directory structure in the following form:
|
||||
/// - 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<ModulesConfig> {
|
||||
let base_dir = match config.service_base_dir {
|
||||
Some(ref base_dir) => base_dir,
|
||||
// TODO: refactor it later
|
||||
None => {
|
||||
return Err(AppServiceError::IOError(String::from(
|
||||
"service_base_dir should be specified",
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let service_dir_path = std::path::Path::new(base_dir).join(service_id);
|
||||
std::fs::create_dir(service_dir_path.clone())?; // will return an error if dir is already exists
|
||||
|
||||
let local_dir_path = service_dir_path.join(SERVICE_LOCAL_DIR_NAME);
|
||||
std::fs::create_dir(local_dir_path.clone())?; // will return an error if dir is already exists
|
||||
|
||||
let tmp_dir_path = service_dir_path.join(SERVICE_TMP_DIR_NAME);
|
||||
std::fs::create_dir(tmp_dir_path.clone())?; // will return an error if dir is already exists
|
||||
|
||||
let local_dir: String = local_dir_path.to_string_lossy().into();
|
||||
let tmp_dir: String = tmp_dir_path.to_string_lossy().into();
|
||||
|
||||
let service_id_env = vec![format!("{}={}", SERVICE_ID_ENV_NAME, service_id).into_bytes()];
|
||||
let preopened_files = vec![local_dir.clone(), tmp_dir.clone()];
|
||||
let mapped_dirs = vec![
|
||||
(String::from(SERVICE_LOCAL_DIR_NAME), local_dir),
|
||||
(String::from(SERVICE_TMP_DIR_NAME), tmp_dir),
|
||||
];
|
||||
|
||||
config.modules_config = config
|
||||
.modules_config
|
||||
.into_iter()
|
||||
.map(|(name, module_config)| {
|
||||
let module_config = module_config
|
||||
.extend_wasi_envs(service_id_env.clone())
|
||||
.extend_wasi_files(preopened_files.clone(), mapped_dirs.clone());
|
||||
|
||||
(name, module_config)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
fn json_to_ivalue(arguments: serde_json::Value) -> Result<Vec<IValue>> {
|
||||
// If arguments are on of: null, [] or {}, avoid calling `to_interface_value`
|
||||
let is_null = arguments.is_null();
|
||||
let is_empty_arr = arguments.as_array().map_or(false, |a| a.is_empty());
|
||||
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!(
|
||||
"can't parse arguments as array of interface types: {}",
|
||||
e
|
||||
))
|
||||
})?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match arguments {
|
||||
Some(IValue::Record(arguments)) => Ok(arguments.into_vec()),
|
||||
// Convert null, [] and {} into vec![]
|
||||
None => Ok(vec![]),
|
||||
other => Err(AppServiceError::InvalidArguments(format!(
|
||||
"expected array of interface values: got {:?}",
|
||||
other
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
@ -14,8 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
use crate::misc::{CoreModulesConfig, make_fce_config};
|
||||
use crate::RawCoreModulesConfig;
|
||||
use crate::misc::ModulesConfig;
|
||||
use crate::Result;
|
||||
|
||||
use super::faas_interface::FaaSInterface;
|
||||
@ -23,7 +22,6 @@ use super::FaaSError;
|
||||
use super::IValue;
|
||||
|
||||
use fce::FCE;
|
||||
use fce::FCEModuleConfig;
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::fs;
|
||||
@ -76,9 +74,6 @@ impl<'a> ModulesLoadStrategy<'a> {
|
||||
|
||||
pub struct FluenceFaaS {
|
||||
fce: FCE,
|
||||
|
||||
// config for code loaded by call_code function
|
||||
faas_code_config: FCEModuleConfig,
|
||||
}
|
||||
|
||||
impl FluenceFaaS {
|
||||
@ -89,9 +84,12 @@ impl FluenceFaaS {
|
||||
}
|
||||
|
||||
/// Creates FaaS from config deserialized from TOML.
|
||||
pub fn with_raw_config(config: RawCoreModulesConfig) -> Result<Self> {
|
||||
let config = crate::misc::from_raw_config(config)?;
|
||||
let modules = config.core_modules_dir.as_ref().map_or(Ok(vec![]), |dir| {
|
||||
pub fn with_raw_config<C>(config: C) -> Result<Self>
|
||||
where
|
||||
C: TryInto<ModulesConfig, Error = FaaSError>,
|
||||
{
|
||||
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)
|
||||
@ -101,48 +99,48 @@ impl FluenceFaaS {
|
||||
pub fn with_modules<I, C>(modules: I, config: C) -> Result<Self>
|
||||
where
|
||||
I: IntoIterator<Item = (String, Vec<u8>)>,
|
||||
C: TryInto<CoreModulesConfig>,
|
||||
C: TryInto<ModulesConfig>,
|
||||
FaaSError: From<C::Error>,
|
||||
{
|
||||
let mut fce = FCE::new();
|
||||
let mut config = config.try_into()?;
|
||||
|
||||
for (name, bytes) in modules {
|
||||
let module_config = crate::misc::make_fce_config(config.modules_config.remove(&name))?;
|
||||
fce.load_module(name.clone(), &bytes, module_config)?;
|
||||
let module_config = match config.modules_config.remove(&name) {
|
||||
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)?;
|
||||
}
|
||||
|
||||
let faas_code_config = make_fce_config(config.rpc_module_config)?;
|
||||
|
||||
Ok(Self {
|
||||
fce,
|
||||
faas_code_config,
|
||||
})
|
||||
Ok(Self { fce })
|
||||
}
|
||||
|
||||
/// Searches for modules in `config.core_modules_dir`, loads only those in the `names` set
|
||||
/// Searches for modules in `config.modules_dir`, loads only those in the `names` set
|
||||
pub fn with_module_names<C>(names: &HashSet<String>, config: C) -> Result<Self>
|
||||
where
|
||||
C: TryInto<CoreModulesConfig>,
|
||||
C: TryInto<ModulesConfig>,
|
||||
FaaSError: From<C::Error>,
|
||||
{
|
||||
let config = config.try_into()?;
|
||||
let modules = config.core_modules_dir.as_ref().map_or(Ok(vec![]), |dir| {
|
||||
let modules = config.modules_dir.as_ref().map_or(Ok(vec![]), |dir| {
|
||||
Self::load_modules(dir, ModulesLoadStrategy::Named(names))
|
||||
})?;
|
||||
|
||||
Self::with_modules::<_, CoreModulesConfig>(modules, config)
|
||||
Self::with_modules::<_, ModulesConfig>(modules, config)
|
||||
}
|
||||
|
||||
/// Loads modules from a directory at a given path. Non-recursive, ignores subdirectories.
|
||||
fn load_modules(
|
||||
core_modules_dir: &str,
|
||||
modules: ModulesLoadStrategy,
|
||||
modules_dir: &str,
|
||||
modules: ModulesLoadStrategy<'_>,
|
||||
) -> Result<Vec<(String, Vec<u8>)>> {
|
||||
use FaaSError::IOError;
|
||||
|
||||
let mut dir_entries = fs::read_dir(core_modules_dir)
|
||||
.map_err(|e| IOError(format!("{}: {}", core_modules_dir, e)))?;
|
||||
let mut dir_entries =
|
||||
fs::read_dir(modules_dir).map_err(|e| IOError(format!("{}: {}", modules_dir, e)))?;
|
||||
|
||||
let loaded = dir_entries.try_fold(vec![], |mut vec, entry| {
|
||||
let entry = entry?;
|
||||
@ -179,34 +177,6 @@ impl FluenceFaaS {
|
||||
Ok(loaded)
|
||||
}
|
||||
|
||||
/// Executes provided Wasm code in the internal environment (with access to module exports).
|
||||
pub fn call_code<S: AsRef<str>>(
|
||||
&mut self,
|
||||
wasm: &[u8],
|
||||
func_name: S,
|
||||
args: &[IValue],
|
||||
) -> Result<Vec<IValue>> {
|
||||
self.call_code_(wasm, func_name.as_ref(), args)
|
||||
}
|
||||
|
||||
pub fn call_code_(
|
||||
&mut self,
|
||||
wasm: &[u8],
|
||||
func_name: &str,
|
||||
args: &[IValue],
|
||||
) -> Result<Vec<IValue>> {
|
||||
// We need this because every wasm code loaded into VM needs a module name
|
||||
let anonymous_module = "anonymous_module_name";
|
||||
|
||||
self.fce
|
||||
.load_module(anonymous_module, wasm, self.faas_code_config.clone())?;
|
||||
|
||||
let call_result = self.fce.call(anonymous_module, func_name, args)?;
|
||||
self.fce.unload_module(anonymous_module)?;
|
||||
|
||||
Ok(call_result)
|
||||
}
|
||||
|
||||
/// Call a specified function of loaded on a startup module by its name.
|
||||
pub fn call_module<MN: AsRef<str>, FN: AsRef<str>>(
|
||||
&mut self,
|
||||
@ -219,8 +189,8 @@ impl FluenceFaaS {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Return all export functions (name and signatures) of loaded on a startup modules.
|
||||
pub fn get_interface(&self) -> FaaSInterface {
|
||||
/// Return all export functions (name and signatures) of loaded modules.
|
||||
pub fn get_interface(&self) -> FaaSInterface<'_> {
|
||||
let modules = self
|
||||
.fce
|
||||
.interface()
|
||||
|
@ -15,7 +15,9 @@
|
||||
*/
|
||||
|
||||
use super::IType;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::Serializer;
|
||||
|
||||
use std::fmt;
|
||||
use std::collections::HashMap;
|
||||
|
@ -13,6 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![deny(
|
||||
dead_code,
|
||||
nonstandard_style,
|
||||
unused_imports,
|
||||
unused_mut,
|
||||
unused_variables,
|
||||
unused_unsafe,
|
||||
unreachable_patterns
|
||||
)]
|
||||
|
||||
mod errors;
|
||||
mod faas;
|
||||
@ -21,12 +31,18 @@ mod misc;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, FaaSError>;
|
||||
|
||||
pub use fce::IValue;
|
||||
pub use fce::{to_interface_value, from_interface_values};
|
||||
pub use fce::IType;
|
||||
|
||||
pub use errors::FaaSError;
|
||||
|
||||
pub use fce::IValue;
|
||||
pub use fce::IType;
|
||||
pub use fce::to_interface_value;
|
||||
pub use fce::from_interface_values;
|
||||
|
||||
pub use faas::FluenceFaaS;
|
||||
pub use faas_interface::FaaSInterface;
|
||||
|
||||
pub use misc::{RawCoreModulesConfig, RawModuleConfig};
|
||||
pub use misc::RawModulesConfig;
|
||||
pub use misc::RawModuleConfig;
|
||||
pub use misc::ModulesConfig;
|
||||
pub use misc::ModuleConfig;
|
||||
pub use misc::WASIConfig;
|
||||
|
@ -26,43 +26,50 @@ use std::convert::TryInto;
|
||||
/*
|
||||
An example of the config:
|
||||
|
||||
core_modules_dir = "wasm/artifacts/wasm_modules"
|
||||
modules_dir = "wasm/artifacts/wasm_modules"
|
||||
service_base_dir = "/Users/user/tmp"
|
||||
|
||||
[[core_module]]
|
||||
[[module]]
|
||||
name = "ipfs_node.wasm"
|
||||
mem_pages_count = 100
|
||||
logger_enabled = true
|
||||
|
||||
[core_module.imports]
|
||||
[module.imports]
|
||||
mysql = "/usr/bin/mysql"
|
||||
ipfs = "/usr/local/bin/ipfs"
|
||||
|
||||
[core_module.wasi]
|
||||
[module.wasi]
|
||||
envs = []
|
||||
preopened_files = ["/Users/user/tmp/"]
|
||||
mapped_dirs = { "tmp" = "/Users/user/tmp" }
|
||||
preopened_files = ["service_id"]
|
||||
# it has to be full path from the right side
|
||||
mapped_dirs = ["tmp" = "/Users/user/tmp"]
|
||||
|
||||
[rpc_module]
|
||||
[default]
|
||||
mem_pages_count = 100
|
||||
logger_enabled = true
|
||||
|
||||
[rpc_module.wasi]
|
||||
[default.imports]
|
||||
mysql = "/usr/bin/mysql"
|
||||
ipfs = "/usr/local/bin/ipfs"
|
||||
|
||||
[default.wasi]
|
||||
envs = []
|
||||
preopened_files = ["/Users/user/tmp"]
|
||||
mapped_dirs = { "tmp" = "/Users/user/tmp" }
|
||||
preopened_files = ["service_id"]
|
||||
mapped_dirs = ["tmp" = "/Users/user/tmp"]
|
||||
*/
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct RawCoreModulesConfig {
|
||||
pub core_modules_dir: Option<String>,
|
||||
pub core_module: Vec<RawModuleConfig>,
|
||||
pub rpc_module: Option<RawRPCModuleConfig>,
|
||||
pub struct RawModulesConfig {
|
||||
pub modules_dir: Option<String>,
|
||||
pub service_base_dir: Option<String>,
|
||||
pub module: Vec<RawModuleConfig>,
|
||||
pub default: Option<RawDefaultModuleConfig>,
|
||||
}
|
||||
|
||||
impl TryInto<CoreModulesConfig> for RawCoreModulesConfig {
|
||||
impl TryInto<ModulesConfig> for RawModulesConfig {
|
||||
type Error = FaaSError;
|
||||
|
||||
fn try_into(self) -> Result<CoreModulesConfig> {
|
||||
fn try_into(self) -> Result<ModulesConfig> {
|
||||
from_raw_config(self)
|
||||
}
|
||||
}
|
||||
@ -76,6 +83,14 @@ pub struct RawModuleConfig {
|
||||
pub wasi: Option<RawWASIConfig>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct RawDefaultModuleConfig {
|
||||
pub mem_pages_count: Option<u32>,
|
||||
pub logger_enabled: Option<bool>,
|
||||
pub imports: Option<toml::value::Table>,
|
||||
pub wasi: Option<RawWASIConfig>,
|
||||
}
|
||||
|
||||
impl RawModuleConfig {
|
||||
pub fn new(name: String) -> Self {
|
||||
Self {
|
||||
@ -95,92 +110,158 @@ pub struct RawWASIConfig {
|
||||
pub mapped_dirs: Option<toml::value::Table>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||
pub struct RawRPCModuleConfig {
|
||||
pub mem_pages_count: Option<u32>,
|
||||
pub logger_enabled: Option<bool>,
|
||||
pub wasi: Option<RawWASIConfig>,
|
||||
}
|
||||
|
||||
/// Describes behaviour of all modules from a node.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct CoreModulesConfig {
|
||||
pub core_modules_dir: Option<String>,
|
||||
pub struct ModulesConfig {
|
||||
/// Used for preparing filesystem on the service initialization stage.
|
||||
pub service_base_dir: Option<String>,
|
||||
|
||||
/// Path to a dir where compiled Wasm modules are located.
|
||||
pub modules_dir: Option<String>,
|
||||
|
||||
/// Settings for a module with particular name.
|
||||
pub modules_config: HashMap<String, ModuleConfig>,
|
||||
pub rpc_module_config: Option<ModuleConfig>,
|
||||
|
||||
/// Settings for a module that name's not been found in modules_config.
|
||||
pub default_modules_config: Option<ModuleConfig>,
|
||||
}
|
||||
|
||||
/// Various settings that could be used to guide FCE how to load a module in a proper way.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ModuleConfig {
|
||||
/// Maximum memory size accessible by a module in Wasm pages (64 Kb).
|
||||
pub mem_pages_count: Option<u32>,
|
||||
pub logger_enabled: Option<bool>,
|
||||
|
||||
/// Defines whether FaaS should provide a special host log_utf8_string function for this module.
|
||||
pub logger_enabled: bool,
|
||||
|
||||
/// A list of CLI host imports that should be provided for this module.
|
||||
pub imports: Option<Vec<(String, String)>>,
|
||||
|
||||
/// A WASI config.
|
||||
pub wasi: Option<WASIConfig>,
|
||||
}
|
||||
|
||||
impl ModuleConfig {
|
||||
pub fn extend_wasi_envs(mut self, new_envs: Vec<Vec<u8>>) -> Self {
|
||||
match &mut self.wasi {
|
||||
Some(WASIConfig {
|
||||
envs: Some(envs), ..
|
||||
}) => envs.extend(new_envs),
|
||||
Some(w @ WASIConfig { envs: None, .. }) => w.envs = Some(new_envs),
|
||||
w @ None => {
|
||||
*w = Some(WASIConfig {
|
||||
envs: Some(new_envs),
|
||||
preopened_files: None,
|
||||
mapped_dirs: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub fn extend_wasi_files(
|
||||
mut self,
|
||||
new_preopened_files: Vec<String>,
|
||||
new_mapped_dirs: Vec<(String, String)>,
|
||||
) -> Self {
|
||||
match &mut self.wasi {
|
||||
Some(WASIConfig {
|
||||
preopened_files,
|
||||
mapped_dirs,
|
||||
..
|
||||
}) => {
|
||||
match preopened_files {
|
||||
Some(files) => files.extend(new_preopened_files),
|
||||
f @ None => *f = Some(new_preopened_files),
|
||||
};
|
||||
match mapped_dirs {
|
||||
Some(dirs) => dirs.extend(new_mapped_dirs),
|
||||
d @ None => *d = Some(new_mapped_dirs),
|
||||
};
|
||||
},
|
||||
w @ None => {
|
||||
*w = Some(WASIConfig {
|
||||
envs: None,
|
||||
preopened_files: Some(new_preopened_files),
|
||||
mapped_dirs: Some(new_mapped_dirs),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct WASIConfig {
|
||||
/// A list of environment variables available for this module.
|
||||
pub envs: Option<Vec<Vec<u8>>>,
|
||||
|
||||
/// A list of files available for this module.
|
||||
/// A loaded module could have access only to files from this list.
|
||||
pub preopened_files: Option<Vec<String>>,
|
||||
|
||||
/// Mapping from a usually short to full file name.
|
||||
pub mapped_dirs: Option<Vec<(String, String)>>,
|
||||
}
|
||||
|
||||
/// Prepare config after parsing it from TOML
|
||||
pub(crate) fn from_raw_config(config: RawCoreModulesConfig) -> Result<CoreModulesConfig> {
|
||||
/// Prepare config after parsing it from TOML.
|
||||
fn from_raw_config(config: RawModulesConfig) -> Result<ModulesConfig> {
|
||||
let service_base_dir = config.service_base_dir;
|
||||
let modules_config = config
|
||||
.core_module
|
||||
.module
|
||||
.into_iter()
|
||||
.map(|module| {
|
||||
let imports = module
|
||||
.imports
|
||||
.map(|import| {
|
||||
Ok(import
|
||||
.into_iter()
|
||||
.map(|(import_func_name, host_cmd)| {
|
||||
let host_cmd = host_cmd.try_into::<String>()?;
|
||||
Ok((import_func_name, host_cmd))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?) as Result<_>
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
let wasi = module.wasi.map(parse_raw_wasi);
|
||||
Ok((
|
||||
module.name,
|
||||
ModuleConfig {
|
||||
mem_pages_count: module.mem_pages_count,
|
||||
logger_enabled: module.logger_enabled,
|
||||
imports,
|
||||
wasi,
|
||||
},
|
||||
))
|
||||
})
|
||||
.map(from_raw_module_config)
|
||||
.collect::<Result<HashMap<_, _>>>()?;
|
||||
|
||||
let rpc_module_config = config.rpc_module.map(|rpc_module| {
|
||||
let wasi = rpc_module.wasi.map(parse_raw_wasi);
|
||||
let default_modules_config = config
|
||||
.default
|
||||
.map(from_raw_default_module_config)
|
||||
.transpose()?;
|
||||
|
||||
ModuleConfig {
|
||||
mem_pages_count: rpc_module.mem_pages_count,
|
||||
logger_enabled: rpc_module.logger_enabled,
|
||||
imports: None,
|
||||
wasi,
|
||||
}
|
||||
});
|
||||
|
||||
Ok(CoreModulesConfig {
|
||||
core_modules_dir: config.core_modules_dir,
|
||||
Ok(ModulesConfig {
|
||||
service_base_dir,
|
||||
modules_dir: config.modules_dir,
|
||||
modules_config,
|
||||
rpc_module_config,
|
||||
default_modules_config,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse config from TOML
|
||||
pub(crate) fn load_config(config_file_path: std::path::PathBuf) -> Result<RawCoreModulesConfig> {
|
||||
/// Parse config from TOML.
|
||||
pub(crate) fn load_config(config_file_path: std::path::PathBuf) -> Result<RawModulesConfig> {
|
||||
let file_content = std::fs::read(config_file_path)?;
|
||||
Ok(from_slice(&file_content)?)
|
||||
}
|
||||
|
||||
fn parse_raw_wasi(wasi: RawWASIConfig) -> WASIConfig {
|
||||
fn from_raw_module_config(config: RawModuleConfig) -> Result<(String, ModuleConfig)> {
|
||||
let imports = config.imports.map(parse_imports).transpose()?;
|
||||
let wasi = config.wasi.map(from_raw_wasi_config);
|
||||
Ok((
|
||||
config.name,
|
||||
ModuleConfig {
|
||||
mem_pages_count: config.mem_pages_count,
|
||||
logger_enabled: config.logger_enabled.unwrap_or_default(),
|
||||
imports,
|
||||
wasi,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn from_raw_default_module_config(config: RawDefaultModuleConfig) -> Result<ModuleConfig> {
|
||||
let imports = config.imports.map(parse_imports).transpose()?;
|
||||
let wasi = config.wasi.map(from_raw_wasi_config);
|
||||
Ok(ModuleConfig {
|
||||
mem_pages_count: config.mem_pages_count,
|
||||
logger_enabled: config.logger_enabled.unwrap_or_default(),
|
||||
imports,
|
||||
wasi,
|
||||
})
|
||||
}
|
||||
|
||||
fn from_raw_wasi_config(wasi: RawWASIConfig) -> WASIConfig {
|
||||
let envs = wasi
|
||||
.envs
|
||||
.map(|env| env.into_iter().map(|e| e.into_bytes()).collect::<Vec<_>>());
|
||||
@ -198,3 +279,13 @@ fn parse_raw_wasi(wasi: RawWASIConfig) -> WASIConfig {
|
||||
mapped_dirs,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_imports(imports: toml::value::Table) -> Result<Vec<(String, String)>> {
|
||||
imports
|
||||
.into_iter()
|
||||
.map(|(import_func_name, host_cmd)| {
|
||||
let host_cmd = host_cmd.try_into::<String>()?;
|
||||
Ok((import_func_name, host_cmd))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ mod config;
|
||||
|
||||
pub(crate) use utils::make_fce_config;
|
||||
pub(crate) use config::load_config;
|
||||
pub(crate) use config::from_raw_config;
|
||||
pub(crate) use config::CoreModulesConfig;
|
||||
|
||||
pub use config::{RawCoreModulesConfig, RawModuleConfig};
|
||||
pub use config::RawModulesConfig;
|
||||
pub use config::RawModuleConfig;
|
||||
pub use config::ModulesConfig;
|
||||
pub use config::ModuleConfig;
|
||||
pub use config::WASIConfig;
|
||||
|
@ -28,8 +28,6 @@ use wasmer_runtime::Func;
|
||||
use wasmer_runtime::error::ResolveError;
|
||||
use wasmer_runtime::types::LocalOrImport;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
// based on Wasmer: https://github.com/wasmerio/wasmer/blob/081f6250e69b98b9f95a8f62ad6d8386534f3279/lib/runtime-core/src/instance.rs#L863
|
||||
/// Extract export function from Wasmer instance by name.
|
||||
pub(crate) unsafe fn get_export_func_by_name<'a, Args, Rets>(
|
||||
@ -95,22 +93,25 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
let typed_func: Func<Args, Rets, wasmer_core::typed_func::Wasm> =
|
||||
let typed_func: Func<'_, Args, Rets, wasmer_core::typed_func::Wasm> =
|
||||
Func::from_raw_parts(func_wasm_inner, export_func_ptr, None, ctx as _);
|
||||
|
||||
Ok(typed_func)
|
||||
}
|
||||
|
||||
/// Make FCE config based on parsed raw config.
|
||||
pub(crate) fn make_fce_config(config: Option<ModuleConfig>) -> crate::Result<FCEModuleConfig> {
|
||||
/// Make FCE config based on parsed config.
|
||||
pub(crate) fn make_fce_config(
|
||||
module_config: Option<ModuleConfig>,
|
||||
) -> crate::Result<FCEModuleConfig> {
|
||||
use super::imports::create_host_import_func;
|
||||
use super::imports::log_utf8_string;
|
||||
use wasmer_core::import::Namespace;
|
||||
use std::path::PathBuf;
|
||||
|
||||
let mut wasm_module_config = FCEModuleConfig::default();
|
||||
|
||||
let module_config = match config {
|
||||
Some(config) => config,
|
||||
let module_config = match module_config {
|
||||
Some(module_config) => module_config,
|
||||
None => return Ok(wasm_module_config),
|
||||
};
|
||||
|
||||
@ -120,10 +121,8 @@ pub(crate) fn make_fce_config(config: Option<ModuleConfig>) -> crate::Result<FCE
|
||||
|
||||
let mut namespace = Namespace::new();
|
||||
|
||||
if let Some(logger_enabled) = module_config.logger_enabled {
|
||||
if logger_enabled {
|
||||
namespace.insert("log_utf8_string", func!(log_utf8_string));
|
||||
}
|
||||
if module_config.logger_enabled {
|
||||
namespace.insert("log_utf8_string", func!(log_utf8_string));
|
||||
}
|
||||
|
||||
if let Some(wasi) = module_config.wasi {
|
||||
@ -141,7 +140,7 @@ pub(crate) fn make_fce_config(config: Option<ModuleConfig>) -> crate::Result<FCE
|
||||
if let Some(mapped_dirs) = wasi.mapped_dirs {
|
||||
wasm_module_config.wasi_mapped_dirs = mapped_dirs
|
||||
.into_iter()
|
||||
.map(|(from, to)| (from, PathBuf::from(to)))
|
||||
.map(|(alias, path)| (alias, PathBuf::from(path)))
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fce-cli"
|
||||
description = "Fluence FCE command line tool for develop services for the Fluence network"
|
||||
description = "Fluence FCE command line tool"
|
||||
version = "0.1.1"
|
||||
authors = ["Fluence Labs"]
|
||||
repository = "https://github.com/fluencelabs/fce/tools/cli"
|
||||
|
Loading…
Reference in New Issue
Block a user