Introduce service (#13)

This commit is contained in:
vms 2020-08-07 11:54:37 +03:00 committed by GitHub
parent 32ffc7c8a7
commit c06dddd5ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 533 additions and 149 deletions

16
Cargo.lock generated
View File

@ -472,6 +472,22 @@ dependencies = [
"fluence-sdk-main", "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]] [[package]]
name = "fluence-faas" name = "fluence-faas"
version = "0.1.0" version = "0.1.0"

View File

@ -13,6 +13,7 @@ members = [
"examples/records/wasm/effector", "examples/records/wasm/effector",
"examples/records/wasm/pure", "examples/records/wasm/pure",
"examples/records/wasm/test-record", "examples/records/wasm/test-record",
"fluence-app-service",
"fluence-faas", "fluence-faas",
"tools/cli", "tools/cli",
] ]

View 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"

View 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).

View 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 {}
}
}

View 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;

View 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
))),
}
}
}

View File

@ -14,8 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
use crate::misc::{CoreModulesConfig, make_fce_config}; use crate::misc::ModulesConfig;
use crate::RawCoreModulesConfig;
use crate::Result; use crate::Result;
use super::faas_interface::FaaSInterface; use super::faas_interface::FaaSInterface;
@ -23,7 +22,6 @@ use super::FaaSError;
use super::IValue; use super::IValue;
use fce::FCE; use fce::FCE;
use fce::FCEModuleConfig;
use std::convert::TryInto; use std::convert::TryInto;
use std::fs; use std::fs;
@ -76,9 +74,6 @@ impl<'a> ModulesLoadStrategy<'a> {
pub struct FluenceFaaS { pub struct FluenceFaaS {
fce: FCE, fce: FCE,
// config for code loaded by call_code function
faas_code_config: FCEModuleConfig,
} }
impl FluenceFaaS { impl FluenceFaaS {
@ -89,9 +84,12 @@ impl FluenceFaaS {
} }
/// Creates FaaS from config deserialized from TOML. /// Creates FaaS from config deserialized from TOML.
pub fn with_raw_config(config: RawCoreModulesConfig) -> Result<Self> { pub fn with_raw_config<C>(config: C) -> Result<Self>
let config = crate::misc::from_raw_config(config)?; where
let modules = config.core_modules_dir.as_ref().map_or(Ok(vec![]), |dir| { 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::load_modules(dir, ModulesLoadStrategy::All)
})?; })?;
Self::with_modules(modules, config) Self::with_modules(modules, config)
@ -101,48 +99,48 @@ impl FluenceFaaS {
pub fn with_modules<I, C>(modules: I, config: C) -> Result<Self> pub fn with_modules<I, C>(modules: I, config: C) -> Result<Self>
where where
I: IntoIterator<Item = (String, Vec<u8>)>, I: IntoIterator<Item = (String, Vec<u8>)>,
C: TryInto<CoreModulesConfig>, C: TryInto<ModulesConfig>,
FaaSError: From<C::Error>, FaaSError: From<C::Error>,
{ {
let mut fce = FCE::new(); let mut fce = FCE::new();
let mut config = config.try_into()?; let mut config = config.try_into()?;
for (name, bytes) in modules { for (name, bytes) in modules {
let module_config = crate::misc::make_fce_config(config.modules_config.remove(&name))?; let module_config = match config.modules_config.remove(&name) {
fce.load_module(name.clone(), &bytes, module_config)?; 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 })
Ok(Self {
fce,
faas_code_config,
})
} }
/// 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> pub fn with_module_names<C>(names: &HashSet<String>, config: C) -> Result<Self>
where where
C: TryInto<CoreModulesConfig>, C: TryInto<ModulesConfig>,
FaaSError: From<C::Error>, FaaSError: From<C::Error>,
{ {
let config = config.try_into()?; 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::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. /// Loads modules from a directory at a given path. Non-recursive, ignores subdirectories.
fn load_modules( fn load_modules(
core_modules_dir: &str, modules_dir: &str,
modules: ModulesLoadStrategy, modules: ModulesLoadStrategy<'_>,
) -> Result<Vec<(String, Vec<u8>)>> { ) -> Result<Vec<(String, Vec<u8>)>> {
use FaaSError::IOError; use FaaSError::IOError;
let mut dir_entries = fs::read_dir(core_modules_dir) let mut dir_entries =
.map_err(|e| IOError(format!("{}: {}", core_modules_dir, e)))?; fs::read_dir(modules_dir).map_err(|e| IOError(format!("{}: {}", modules_dir, e)))?;
let loaded = dir_entries.try_fold(vec![], |mut vec, entry| { let loaded = dir_entries.try_fold(vec![], |mut vec, entry| {
let entry = entry?; let entry = entry?;
@ -179,34 +177,6 @@ impl FluenceFaaS {
Ok(loaded) 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. /// Call a specified function of loaded on a startup module by its name.
pub fn call_module<MN: AsRef<str>, FN: AsRef<str>>( pub fn call_module<MN: AsRef<str>, FN: AsRef<str>>(
&mut self, &mut self,
@ -219,8 +189,8 @@ impl FluenceFaaS {
.map_err(Into::into) .map_err(Into::into)
} }
/// Return all export functions (name and signatures) of loaded on a startup modules. /// Return all export functions (name and signatures) of loaded modules.
pub fn get_interface(&self) -> FaaSInterface { pub fn get_interface(&self) -> FaaSInterface<'_> {
let modules = self let modules = self
.fce .fce
.interface() .interface()

View File

@ -15,7 +15,9 @@
*/ */
use super::IType; use super::IType;
use serde::{Serialize, Serializer};
use serde::Serialize;
use serde::Serializer;
use std::fmt; use std::fmt;
use std::collections::HashMap; use std::collections::HashMap;

View File

@ -13,6 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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 errors;
mod faas; mod faas;
@ -21,12 +31,18 @@ mod misc;
pub(crate) type Result<T> = std::result::Result<T, FaaSError>; 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 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::FluenceFaaS;
pub use faas_interface::FaaSInterface; 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;

View File

@ -26,43 +26,50 @@ use std::convert::TryInto;
/* /*
An example of the config: 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" name = "ipfs_node.wasm"
mem_pages_count = 100 mem_pages_count = 100
logger_enabled = true logger_enabled = true
[core_module.imports] [module.imports]
mysql = "/usr/bin/mysql" mysql = "/usr/bin/mysql"
ipfs = "/usr/local/bin/ipfs" ipfs = "/usr/local/bin/ipfs"
[core_module.wasi] [module.wasi]
envs = [] envs = []
preopened_files = ["/Users/user/tmp/"] preopened_files = ["service_id"]
mapped_dirs = { "tmp" = "/Users/user/tmp" } # it has to be full path from the right side
mapped_dirs = ["tmp" = "/Users/user/tmp"]
[rpc_module] [default]
mem_pages_count = 100 mem_pages_count = 100
logger_enabled = true logger_enabled = true
[rpc_module.wasi] [default.imports]
mysql = "/usr/bin/mysql"
ipfs = "/usr/local/bin/ipfs"
[default.wasi]
envs = [] envs = []
preopened_files = ["/Users/user/tmp"] preopened_files = ["service_id"]
mapped_dirs = { "tmp" = "/Users/user/tmp" } mapped_dirs = ["tmp" = "/Users/user/tmp"]
*/ */
#[derive(Deserialize, Serialize, Debug, Clone, Default)] #[derive(Deserialize, Serialize, Debug, Clone, Default)]
pub struct RawCoreModulesConfig { pub struct RawModulesConfig {
pub core_modules_dir: Option<String>, pub modules_dir: Option<String>,
pub core_module: Vec<RawModuleConfig>, pub service_base_dir: Option<String>,
pub rpc_module: Option<RawRPCModuleConfig>, pub module: Vec<RawModuleConfig>,
pub default: Option<RawDefaultModuleConfig>,
} }
impl TryInto<CoreModulesConfig> for RawCoreModulesConfig { impl TryInto<ModulesConfig> for RawModulesConfig {
type Error = FaaSError; type Error = FaaSError;
fn try_into(self) -> Result<CoreModulesConfig> { fn try_into(self) -> Result<ModulesConfig> {
from_raw_config(self) from_raw_config(self)
} }
} }
@ -76,6 +83,14 @@ pub struct RawModuleConfig {
pub wasi: Option<RawWASIConfig>, 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 { impl RawModuleConfig {
pub fn new(name: String) -> Self { pub fn new(name: String) -> Self {
Self { Self {
@ -95,92 +110,158 @@ pub struct RawWASIConfig {
pub mapped_dirs: Option<toml::value::Table>, pub mapped_dirs: Option<toml::value::Table>,
} }
#[derive(Deserialize, Serialize, Debug, Clone, Default)] /// Describes behaviour of all modules from a node.
pub struct RawRPCModuleConfig {
pub mem_pages_count: Option<u32>,
pub logger_enabled: Option<bool>,
pub wasi: Option<RawWASIConfig>,
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct CoreModulesConfig { pub struct ModulesConfig {
pub core_modules_dir: Option<String>, /// 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 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)] #[derive(Debug, Clone, Default)]
pub struct ModuleConfig { pub struct ModuleConfig {
/// Maximum memory size accessible by a module in Wasm pages (64 Kb).
pub mem_pages_count: Option<u32>, 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)>>, pub imports: Option<Vec<(String, String)>>,
/// A WASI config.
pub wasi: Option<WASIConfig>, 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)] #[derive(Debug, Clone, Default)]
pub struct WASIConfig { pub struct WASIConfig {
/// A list of environment variables available for this module.
pub envs: Option<Vec<Vec<u8>>>, 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>>, pub preopened_files: Option<Vec<String>>,
/// Mapping from a usually short to full file name.
pub mapped_dirs: Option<Vec<(String, String)>>, pub mapped_dirs: Option<Vec<(String, String)>>,
} }
/// Prepare config after parsing it from TOML /// Prepare config after parsing it from TOML.
pub(crate) fn from_raw_config(config: RawCoreModulesConfig) -> Result<CoreModulesConfig> { fn from_raw_config(config: RawModulesConfig) -> Result<ModulesConfig> {
let service_base_dir = config.service_base_dir;
let modules_config = config let modules_config = config
.core_module .module
.into_iter() .into_iter()
.map(|module| { .map(from_raw_module_config)
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,
},
))
})
.collect::<Result<HashMap<_, _>>>()?; .collect::<Result<HashMap<_, _>>>()?;
let rpc_module_config = config.rpc_module.map(|rpc_module| { let default_modules_config = config
let wasi = rpc_module.wasi.map(parse_raw_wasi); .default
.map(from_raw_default_module_config)
.transpose()?;
ModuleConfig { Ok(ModulesConfig {
mem_pages_count: rpc_module.mem_pages_count, service_base_dir,
logger_enabled: rpc_module.logger_enabled, modules_dir: config.modules_dir,
imports: None,
wasi,
}
});
Ok(CoreModulesConfig {
core_modules_dir: config.core_modules_dir,
modules_config, modules_config,
rpc_module_config, default_modules_config,
}) })
} }
/// Parse config from TOML /// Parse config from TOML.
pub(crate) fn load_config(config_file_path: std::path::PathBuf) -> Result<RawCoreModulesConfig> { pub(crate) fn load_config(config_file_path: std::path::PathBuf) -> Result<RawModulesConfig> {
let file_content = std::fs::read(config_file_path)?; let file_content = std::fs::read(config_file_path)?;
Ok(from_slice(&file_content)?) 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 let envs = wasi
.envs .envs
.map(|env| env.into_iter().map(|e| e.into_bytes()).collect::<Vec<_>>()); .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, 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<_>>>()
}

View File

@ -4,7 +4,9 @@ mod config;
pub(crate) use utils::make_fce_config; pub(crate) use utils::make_fce_config;
pub(crate) use config::load_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;

View File

@ -28,8 +28,6 @@ use wasmer_runtime::Func;
use wasmer_runtime::error::ResolveError; use wasmer_runtime::error::ResolveError;
use wasmer_runtime::types::LocalOrImport; 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 // 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. /// Extract export function from Wasmer instance by name.
pub(crate) unsafe fn get_export_func_by_name<'a, Args, Rets>( 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 _); Func::from_raw_parts(func_wasm_inner, export_func_ptr, None, ctx as _);
Ok(typed_func) Ok(typed_func)
} }
/// Make FCE config based on parsed raw config. /// Make FCE config based on parsed config.
pub(crate) fn make_fce_config(config: Option<ModuleConfig>) -> crate::Result<FCEModuleConfig> { pub(crate) fn make_fce_config(
module_config: Option<ModuleConfig>,
) -> crate::Result<FCEModuleConfig> {
use super::imports::create_host_import_func; use super::imports::create_host_import_func;
use super::imports::log_utf8_string; use super::imports::log_utf8_string;
use wasmer_core::import::Namespace; use wasmer_core::import::Namespace;
use std::path::PathBuf;
let mut wasm_module_config = FCEModuleConfig::default(); let mut wasm_module_config = FCEModuleConfig::default();
let module_config = match config { let module_config = match module_config {
Some(config) => config, Some(module_config) => module_config,
None => return Ok(wasm_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(); let mut namespace = Namespace::new();
if let Some(logger_enabled) = module_config.logger_enabled { if module_config.logger_enabled {
if logger_enabled { namespace.insert("log_utf8_string", func!(log_utf8_string));
namespace.insert("log_utf8_string", func!(log_utf8_string));
}
} }
if let Some(wasi) = module_config.wasi { 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 { if let Some(mapped_dirs) = wasi.mapped_dirs {
wasm_module_config.wasi_mapped_dirs = mapped_dirs wasm_module_config.wasi_mapped_dirs = mapped_dirs
.into_iter() .into_iter()
.map(|(from, to)| (from, PathBuf::from(to))) .map(|(alias, path)| (alias, PathBuf::from(path)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "fce-cli" 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" version = "0.1.1"
authors = ["Fluence Labs"] authors = ["Fluence Labs"]
repository = "https://github.com/fluencelabs/fce/tools/cli" repository = "https://github.com/fluencelabs/fce/tools/cli"