zenoh_plugin_trait/
manager.rs

1// Copyright (c) 2023 ZettaScale Technology
2//
3// This program and the accompanying materials are made available under the
4// terms of the Eclipse Public License 2.0 which is available at
5// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
6// which is available at https://www.apache.org/licenses/LICENSE-2.0.
7//
8// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
9//
10// Contributors:
11//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
12//
13mod dynamic_plugin;
14mod static_plugin;
15
16use zenoh_keyexpr::keyexpr;
17use zenoh_result::ZResult;
18use zenoh_util::LibLoader;
19
20use self::{
21    dynamic_plugin::{DynamicPlugin, DynamicPluginSource},
22    static_plugin::StaticPlugin,
23};
24use crate::*;
25
26pub trait DeclaredPlugin<StartArgs, Instance>: PluginStatus {
27    fn as_status(&self) -> &dyn PluginStatus;
28    fn load(&mut self) -> ZResult<Option<&mut dyn LoadedPlugin<StartArgs, Instance>>>;
29    fn loaded(&self) -> Option<&dyn LoadedPlugin<StartArgs, Instance>>;
30    fn loaded_mut(&mut self) -> Option<&mut dyn LoadedPlugin<StartArgs, Instance>>;
31}
32pub trait LoadedPlugin<StartArgs, Instance>: PluginStatus {
33    fn as_status(&self) -> &dyn PluginStatus;
34    fn required(&self) -> bool;
35    fn start(&mut self, args: &StartArgs) -> ZResult<&mut dyn StartedPlugin<StartArgs, Instance>>;
36    fn started(&self) -> Option<&dyn StartedPlugin<StartArgs, Instance>>;
37    fn started_mut(&mut self) -> Option<&mut dyn StartedPlugin<StartArgs, Instance>>;
38}
39
40pub trait StartedPlugin<StartArgs, Instance>: PluginStatus {
41    fn as_status(&self) -> &dyn PluginStatus;
42    fn stop(&mut self);
43    fn instance(&self) -> &Instance;
44    fn instance_mut(&mut self) -> &mut Instance;
45}
46
47struct PluginRecord<StartArgs: PluginStartArgs, Instance: PluginInstance>(
48    Box<dyn DeclaredPlugin<StartArgs, Instance> + Send + Sync>,
49);
50
51impl<StartArgs: PluginStartArgs, Instance: PluginInstance> PluginRecord<StartArgs, Instance> {
52    fn new<P: DeclaredPlugin<StartArgs, Instance> + Send + Sync + 'static>(plugin: P) -> Self {
53        Self(Box::new(plugin))
54    }
55}
56
57impl<StartArgs: PluginStartArgs, Instance: PluginInstance> PluginStatus
58    for PluginRecord<StartArgs, Instance>
59{
60    fn name(&self) -> &str {
61        self.0.name()
62    }
63
64    fn id(&self) -> &str {
65        self.0.id()
66    }
67
68    fn version(&self) -> Option<&str> {
69        self.0.version()
70    }
71    fn long_version(&self) -> Option<&str> {
72        self.0.long_version()
73    }
74    fn path(&self) -> &str {
75        self.0.path()
76    }
77    fn state(&self) -> PluginState {
78        self.0.state()
79    }
80    fn report(&self) -> PluginReport {
81        self.0.report()
82    }
83}
84
85impl<StartArgs: PluginStartArgs, Instance: PluginInstance> DeclaredPlugin<StartArgs, Instance>
86    for PluginRecord<StartArgs, Instance>
87{
88    fn as_status(&self) -> &dyn PluginStatus {
89        self
90    }
91    fn load(&mut self) -> ZResult<Option<&mut dyn LoadedPlugin<StartArgs, Instance>>> {
92        self.0.load()
93    }
94    fn loaded(&self) -> Option<&dyn LoadedPlugin<StartArgs, Instance>> {
95        self.0.loaded()
96    }
97    fn loaded_mut(&mut self) -> Option<&mut dyn LoadedPlugin<StartArgs, Instance>> {
98        self.0.loaded_mut()
99    }
100}
101
102/// A plugins manager that handles starting and stopping plugins.
103/// Plugins can be loaded from shared libraries using [`Self::declare_dynamic_plugin_by_name`] or [`Self::declare_dynamic_plugin_by_paths`], or added directly from the binary if available using [`Self::declare_static_plugin`].
104pub struct PluginsManager<StartArgs: PluginStartArgs, Instance: PluginInstance> {
105    default_lib_prefix: String,
106    loader: Option<LibLoader>,
107    plugins: Vec<PluginRecord<StartArgs, Instance>>,
108}
109
110impl<StartArgs: PluginStartArgs + 'static, Instance: PluginInstance + 'static>
111    PluginsManager<StartArgs, Instance>
112{
113    /// Constructs a new plugin manager with dynamic library loading enabled.
114    pub fn dynamic<S: Into<String>>(loader: LibLoader, default_lib_prefix: S) -> Self {
115        PluginsManager {
116            default_lib_prefix: default_lib_prefix.into(),
117            loader: Some(loader),
118            plugins: Default::default(),
119        }
120    }
121    /// Constructs a new plugin manager with dynamic library loading disabled.
122    pub fn static_plugins_only() -> Self {
123        PluginsManager {
124            default_lib_prefix: String::new(),
125            loader: None,
126            plugins: Default::default(),
127        }
128    }
129
130    /// Adds a statically linked plugin to the manager.
131    pub fn declare_static_plugin<
132        P: Plugin<StartArgs = StartArgs, Instance = Instance> + Send + Sync,
133        S: Into<String>,
134    >(
135        &mut self,
136        id: S,
137        required: bool,
138    ) {
139        let id = id.into();
140        let plugin_loader: StaticPlugin<StartArgs, Instance, P> =
141            StaticPlugin::new(id.clone(), required);
142
143        if self.get_plugin_index(&id).is_some() {
144            tracing::warn!(
145                "Duplicate plugin with ID: {id}, only the last declared one will be loaded"
146            )
147        }
148
149        self.plugins.push(PluginRecord::new(plugin_loader));
150        tracing::debug!(
151            "Declared static plugin Id:{} - Name:{}",
152            self.plugins.last().unwrap().id(),
153            self.plugins.last().unwrap().name()
154        );
155    }
156
157    /// Add dynamic plugin to the manager by name, automatically prepending the default library prefix
158    pub fn declare_dynamic_plugin_by_name<S: Into<String>>(
159        &mut self,
160        id: S,
161        plugin_name: S,
162        required: bool,
163    ) -> ZResult<&mut dyn DeclaredPlugin<StartArgs, Instance>> {
164        let plugin_name = plugin_name.into();
165        let id = id.into();
166        let libplugin_name = format!("{}{}", self.default_lib_prefix, plugin_name);
167        let libloader = self
168            .loader
169            .as_ref()
170            .ok_or("Dynamic plugin loading is disabled")?
171            .clone();
172        tracing::debug!(
173            "Declared dynamic plugin {} by name {}",
174            &id,
175            &libplugin_name
176        );
177        let loader = DynamicPlugin::new(
178            plugin_name,
179            id.clone(),
180            DynamicPluginSource::ByName((libloader, libplugin_name)),
181            required,
182        );
183
184        if self.get_plugin_index(&id).is_some() {
185            tracing::warn!(
186                "Duplicate plugin with ID: {id}, only the last declared one will be loaded"
187            )
188        }
189        self.plugins.push(PluginRecord::new(loader));
190        Ok(self.plugins.last_mut().unwrap())
191    }
192
193    /// Add first available dynamic plugin from the list of paths to the plugin files
194    pub fn declare_dynamic_plugin_by_paths<S: Into<String>, P: AsRef<str> + std::fmt::Debug>(
195        &mut self,
196        name: S,
197        id: S,
198        paths: &[P],
199        required: bool,
200    ) -> ZResult<&mut dyn DeclaredPlugin<StartArgs, Instance>> {
201        let name = name.into();
202        let id = id.into();
203        let paths = paths.iter().map(|p| p.as_ref().into()).collect();
204        tracing::debug!("Declared dynamic plugin {} by paths {:?}", &id, &paths);
205        let loader = DynamicPlugin::new(
206            name,
207            id.clone(),
208            DynamicPluginSource::ByPaths(paths),
209            required,
210        );
211
212        if self.get_plugin_index(&id).is_some() {
213            tracing::warn!(
214                "Duplicate plugin with ID: {id}, only the last declared one will be loaded"
215            )
216        }
217
218        self.plugins.push(PluginRecord::new(loader));
219        Ok(self.plugins.last_mut().unwrap())
220    }
221
222    fn get_plugin_index(&self, id: &str) -> Option<usize> {
223        self.plugins.iter().position(|p| p.id() == id)
224    }
225
226    /// Lists all plugins
227    pub fn declared_plugins_iter(
228        &self,
229    ) -> impl Iterator<Item = &dyn DeclaredPlugin<StartArgs, Instance>> + '_ {
230        self.plugins
231            .iter()
232            .map(|p| p as &dyn DeclaredPlugin<StartArgs, Instance>)
233    }
234
235    /// Lists all plugins mutable
236    pub fn declared_plugins_iter_mut(
237        &mut self,
238    ) -> impl Iterator<Item = &mut dyn DeclaredPlugin<StartArgs, Instance>> + '_ {
239        self.plugins
240            .iter_mut()
241            .map(|p| p as &mut dyn DeclaredPlugin<StartArgs, Instance>)
242    }
243
244    /// Lists the loaded plugins
245    pub fn loaded_plugins_iter(
246        &self,
247    ) -> impl Iterator<Item = &dyn LoadedPlugin<StartArgs, Instance>> + '_ {
248        self.declared_plugins_iter().filter_map(|p| p.loaded())
249    }
250
251    /// Lists the loaded plugins mutable
252    pub fn loaded_plugins_iter_mut(
253        &mut self,
254    ) -> impl Iterator<Item = &mut dyn LoadedPlugin<StartArgs, Instance>> + '_ {
255        self.declared_plugins_iter_mut()
256            .filter_map(|p| p.loaded_mut())
257    }
258
259    /// Lists the started plugins
260    pub fn started_plugins_iter(
261        &self,
262    ) -> impl Iterator<Item = &dyn StartedPlugin<StartArgs, Instance>> + '_ {
263        self.loaded_plugins_iter().filter_map(|p| p.started())
264    }
265
266    /// Lists the started plugins mutable
267    pub fn started_plugins_iter_mut(
268        &mut self,
269    ) -> impl Iterator<Item = &mut dyn StartedPlugin<StartArgs, Instance>> + '_ {
270        self.loaded_plugins_iter_mut()
271            .filter_map(|p| p.started_mut())
272    }
273
274    /// Returns single plugin record by id
275    pub fn plugin(&self, id: &str) -> Option<&dyn DeclaredPlugin<StartArgs, Instance>> {
276        let index = self.get_plugin_index(id)?;
277        Some(&self.plugins[index])
278    }
279
280    /// Returns mutable plugin record by id
281    pub fn plugin_mut(&mut self, id: &str) -> Option<&mut dyn DeclaredPlugin<StartArgs, Instance>> {
282        let index = self.get_plugin_index(id)?;
283        Some(&mut self.plugins[index])
284    }
285
286    /// Returns loaded plugin record by id
287    pub fn loaded_plugin(&self, id: &str) -> Option<&dyn LoadedPlugin<StartArgs, Instance>> {
288        self.plugin(id)?.loaded()
289    }
290
291    /// Returns mutable loaded plugin record by id
292    pub fn loaded_plugin_mut(
293        &mut self,
294        id: &str,
295    ) -> Option<&mut dyn LoadedPlugin<StartArgs, Instance>> {
296        self.plugin_mut(id)?.loaded_mut()
297    }
298
299    /// Returns started plugin record by id
300    pub fn started_plugin(&self, id: &str) -> Option<&dyn StartedPlugin<StartArgs, Instance>> {
301        self.loaded_plugin(id)?.started()
302    }
303
304    /// Returns mutable started plugin record by id
305    pub fn started_plugin_mut(
306        &mut self,
307        id: &str,
308    ) -> Option<&mut dyn StartedPlugin<StartArgs, Instance>> {
309        self.loaded_plugin_mut(id)?.started_mut()
310    }
311}
312
313impl<StartArgs: PluginStartArgs + 'static, Instance: PluginInstance + 'static> PluginControl
314    for PluginsManager<StartArgs, Instance>
315{
316    fn plugins_status(&self, names: &keyexpr) -> Vec<PluginStatusRec<'_>> {
317        tracing::debug!(
318            "Plugin manager with prefix `{}` : requested plugins_status {:?}",
319            self.default_lib_prefix,
320            names
321        );
322        let mut plugins = Vec::new();
323        for plugin in self.declared_plugins_iter() {
324            let id = unsafe { keyexpr::from_str_unchecked(plugin.id()) };
325            if names.includes(id) {
326                let status = PluginStatusRec::new(plugin.as_status());
327                plugins.push(status);
328            }
329            // for running plugins append their subplugins prepended with the running plugin name
330            if let Some(plugin) = plugin.loaded() {
331                if let Some(plugin) = plugin.started() {
332                    if let [names, ..] = names.strip_prefix(id)[..] {
333                        plugins.append(
334                            &mut plugin
335                                .instance()
336                                .plugins_status(names)
337                                .into_iter()
338                                .map(|s| s.prepend_name(id))
339                                .collect(),
340                        );
341                    }
342                }
343            }
344        }
345        plugins
346    }
347}