fluence_app_service/
service.rs

1/*
2 * Copyright 2020 Fluence Labs Limited
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use crate::Result;
18use crate::config::AppServiceConfig;
19use crate::MemoryStats;
20use crate::service_interface::ServiceInterface;
21use super::AppServiceError;
22
23#[cfg(feature = "raw-module-api")]
24use marine_wasm_backend_traits::WasiState;
25use marine_wasm_backend_traits::WasmBackend;
26use marine::generic::Marine;
27use marine::generic::MarineModuleConfig;
28use marine::MarineError;
29use marine::MError;
30use marine::IValue;
31
32use serde_json::Value as JValue;
33
34use std::convert::TryInto;
35use std::collections::HashMap;
36use std::path::Path;
37use std::io::ErrorKind;
38
39const SERVICE_ID_ENV_NAME: &str = "service_id";
40
41pub struct AppService<WB: WasmBackend> {
42    marine: marine::generic::Marine<WB>,
43    facade_module_name: String,
44}
45
46impl<WB: WasmBackend> AppService<WB> {
47    /// Create Service with given modules and service id.
48    pub async fn new<C, S>(config: C, service_id: S, envs: HashMap<String, String>) -> Result<Self>
49    where
50        C: TryInto<AppServiceConfig<WB>>,
51        S: Into<String>,
52        AppServiceError: From<C::Error>,
53    {
54        let backend = <WB as WasmBackend>::new_async()
55            .map_err(|e| MarineError::EngineError(MError::WasmBackendError(e)))?;
56
57        Self::new_with_backend(backend, config, service_id, envs).await
58    }
59
60    pub async fn new_with_backend<C, S>(
61        backend: WB,
62        config: C,
63        service_id: S,
64        envs: HashMap<String, String>,
65    ) -> Result<Self>
66    where
67        C: TryInto<AppServiceConfig<WB>>,
68        S: Into<String>,
69        AppServiceError: From<C::Error>,
70    {
71        let mut config: AppServiceConfig<WB> = config.try_into()?;
72        let facade_module_name = config
73            .marine_config
74            .modules_config
75            .last()
76            .ok_or_else(|| {
77                AppServiceError::ConfigParseError(String::from(
78                    "config should contain at least one module",
79                ))
80            })?
81            .import_name
82            .clone();
83
84        let service_id = service_id.into();
85        Self::set_env_and_dirs(&mut config, service_id, envs)?;
86
87        let marine = Marine::with_raw_config(backend, config.marine_config).await?;
88
89        Ok(Self {
90            marine,
91            facade_module_name,
92        })
93    }
94
95    /// Call a specified function of loaded module by its name with arguments in json format.
96    pub async fn call_async(
97        &mut self,
98        func_name: impl AsRef<str>,
99        arguments: JValue,
100        call_parameters: crate::CallParameters,
101    ) -> Result<JValue> {
102        self.marine
103            .call_with_json_async(
104                &self.facade_module_name,
105                func_name,
106                arguments,
107                call_parameters,
108            )
109            .await
110            .map_err(Into::into)
111    }
112
113    /// Call a specified function of loaded module by its name with arguments in IValue format.
114    pub async fn call_with_ivalues_async(
115        &mut self,
116        func_name: impl AsRef<str>,
117        arguments: &[IValue],
118        call_parameters: crate::CallParameters,
119    ) -> Result<Vec<IValue>> {
120        self.marine
121            .call_with_ivalues_async(
122                &self.facade_module_name,
123                func_name,
124                arguments,
125                call_parameters,
126            )
127            .await
128            .map_err(Into::into)
129    }
130
131    /// Return interface (function signatures and record types) of this service.
132    pub fn get_interface(&self) -> ServiceInterface {
133        use crate::service_interface::into_service_interface;
134
135        let marine_facade_interface = self
136            .marine
137            .get_interface()
138            .modules
139            .remove(self.facade_module_name.as_str())
140            // facade module must be loaded into FaaS, so unwrap is safe here
141            .unwrap();
142
143        into_service_interface(marine_facade_interface)
144    }
145
146    /// Prepare service before starting by:
147    ///  1. rooting all mapped directories at service_working_dir, keeping absolute paths as-is
148    ///  2. adding service_id to environment variables
149    fn set_env_and_dirs(
150        config: &mut AppServiceConfig<WB>,
151        service_id: String,
152        mut envs: HashMap<String, String>,
153    ) -> Result<()> {
154        let working_dir = &config.service_working_dir;
155
156        envs.insert(SERVICE_ID_ENV_NAME.to_string(), service_id);
157
158        for module in &mut config.marine_config.modules_config {
159            module.config.extend_wasi_envs(envs.clone());
160            // Moves relative paths in mapped dirs to the &working dir, keeping old aliases.
161            module.config.root_wasi_files_at(working_dir);
162
163            // Create all mapped directories if they do not exist
164            // Needed to provide ability to run the same services both in mrepl and rust-peer
165            create_wasi_dirs(&module.config)?;
166        }
167        Ok(())
168    }
169
170    /// Return statistics of Wasm modules heap footprint.
171    /// This operation is cheap.
172    pub fn module_memory_stats(&self) -> MemoryStats<'_> {
173        self.marine.module_memory_stats()
174    }
175}
176
177// This API is intended for testing purposes (mostly in Marine REPL)
178#[cfg(feature = "raw-module-api")]
179impl<WB: WasmBackend> AppService<WB> {
180    pub async fn new_with_empty_facade<C, S>(
181        backend: WB,
182        config: C,
183        service_id: S,
184        envs: HashMap<String, String>,
185    ) -> Result<Self>
186    where
187        S: Into<String>,
188        C: TryInto<AppServiceConfig<WB>>,
189        AppServiceError: From<C::Error>,
190    {
191        let mut config: AppServiceConfig<WB> = config.try_into()?;
192        let service_id = service_id.into();
193        Self::set_env_and_dirs(&mut config, service_id, envs)?;
194
195        let marine = Marine::with_raw_config(backend, config.marine_config).await?;
196
197        Ok(Self {
198            marine,
199            facade_module_name: String::new(),
200        })
201    }
202
203    pub async fn call_module(
204        &mut self,
205        module_name: impl AsRef<str>,
206        func_name: impl AsRef<str>,
207        arguments: JValue,
208        call_parameters: crate::CallParameters,
209    ) -> Result<JValue> {
210        self.marine
211            .call_with_json_async(module_name, func_name, arguments, call_parameters)
212            .await
213            .map_err(Into::into)
214    }
215
216    pub async fn load_module<C, S>(
217        &mut self,
218        name: S,
219        wasm_bytes: &[u8],
220        config: Option<C>,
221    ) -> Result<()>
222    where
223        S: Into<String>,
224        C: TryInto<marine::generic::MarineModuleConfig<WB>>,
225        marine::MarineError: From<C::Error>,
226    {
227        self.marine
228            .load_module(name, wasm_bytes, config)
229            .await
230            .map_err(Into::into)
231    }
232
233    pub fn unload_module(&mut self, module_name: impl AsRef<str>) -> Result<()> {
234        self.marine.unload_module(module_name).map_err(Into::into)
235    }
236
237    /// Return raw interface of the underlying [[Marine]] instance
238    pub fn get_full_interface(&self) -> marine::MarineInterface<'_> {
239        self.marine.get_interface()
240    }
241
242    /// Return
243    pub fn get_wasi_state(
244        &mut self,
245        module_name: impl AsRef<str>,
246    ) -> Result<Box<dyn WasiState + '_>> {
247        self.marine
248            .module_wasi_state(module_name)
249            .map_err(Into::into)
250    }
251}
252
253fn create_wasi_dirs<WB: WasmBackend>(config: &MarineModuleConfig<WB>) -> Result<()> {
254    if let Some(wasi_config) = &config.wasi {
255        for dir in wasi_config.mapped_dirs.values() {
256            create(dir)?;
257        }
258    }
259
260    Ok(())
261}
262
263fn create(dir: &Path) -> Result<()> {
264    match std::fs::create_dir_all(dir) {
265        Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()),
266        Err(err) => Err(AppServiceError::CreateDir {
267            err,
268            path: dir.to_owned(),
269        }),
270        _ => Ok(()),
271    }
272}