inspect_validator/
puppet.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::data::{self, Data, LazyNode};
6use super::metrics::Metrics;
7use super::PUPPET_MONIKER;
8use anyhow::{format_err, Error};
9use fuchsia_component::client as fclient;
10use serde::Serialize;
11use zx::{self as zx, Vmo};
12use {fidl_diagnostics_validate as validate, fidl_fuchsia_inspect as fidl_inspect};
13
14pub const VMO_SIZE: u64 = 4096;
15
16#[derive(Debug)]
17pub struct Config {
18    pub diff_type: DiffType,
19    pub printable_name: String,
20    pub has_runner_node: bool,
21    pub test_archive: bool,
22}
23
24/// When reporting a discrepancy between local and remote Data trees, should the output include:
25/// - The full rendering of both trees?
26/// - The condensed diff between the trees? (This may still be quite large.)
27/// - Both full and condensed renderings?
28#[derive(Clone, Copy, Debug, Default, Serialize)]
29pub enum DiffType {
30    #[default]
31    Full,
32    Diff,
33    Both,
34}
35
36impl From<Option<validate::DiffType>> for DiffType {
37    fn from(original: Option<validate::DiffType>) -> Self {
38        match original {
39            Some(validate::DiffType::Diff) => Self::Diff,
40            Some(validate::DiffType::Both) => Self::Both,
41            _ => Self::Full,
42        }
43    }
44}
45
46pub struct Puppet {
47    pub vmo: Vmo,
48    // Need to remember the connection to avoid dropping the VMO
49    connection: Connection,
50    // A printable name for output to the user.
51    pub config: Config,
52}
53
54impl Puppet {
55    pub async fn apply(
56        &mut self,
57        action: &mut validate::Action,
58    ) -> Result<validate::TestResult, Error> {
59        Ok(self.connection.fidl.act(action).await?)
60    }
61
62    pub async fn apply_lazy(
63        &mut self,
64        lazy_action: &mut validate::LazyAction,
65    ) -> Result<validate::TestResult, Error> {
66        match &self.connection.root_link_channel {
67            Some(_) => Ok(self.connection.fidl.act_lazy(lazy_action).await?),
68            None => Ok(validate::TestResult::Unimplemented),
69        }
70    }
71
72    pub async fn publish(&mut self) -> Result<validate::TestResult, Error> {
73        Ok(self.connection.fidl.publish().await?)
74    }
75
76    pub async fn connect() -> Result<Self, Error> {
77        Puppet::initialize_with_connection(Connection::connect().await?).await
78    }
79
80    pub(crate) async fn shutdown(self) {
81        let lifecycle_controller =
82            fclient::connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().unwrap();
83        lifecycle_controller.stop_instance(&format!("./{PUPPET_MONIKER}")).await.unwrap().unwrap();
84    }
85
86    /// Get the printable name associated with this puppet/test
87    pub fn printable_name(&self) -> &str {
88        &self.config.printable_name
89    }
90
91    #[cfg(test)]
92    pub async fn connect_local(local_fidl: validate::InspectPuppetProxy) -> Result<Puppet, Error> {
93        let mut puppet = Puppet::initialize_with_connection(Connection::new(local_fidl)).await?;
94        puppet.config.test_archive = false;
95        Ok(puppet)
96    }
97
98    async fn initialize_with_connection(mut connection: Connection) -> Result<Puppet, Error> {
99        Ok(Puppet {
100            vmo: connection.initialize_vmo().await?,
101            config: connection.get_config().await?,
102            connection,
103        })
104    }
105
106    pub async fn read_data(&self) -> Result<Data, Error> {
107        Ok(match &self.connection.root_link_channel {
108            None => data::Scanner::try_from(&self.vmo)?.data(),
109            Some(root_link_channel) => {
110                let vmo_tree = LazyNode::new(root_link_channel.clone()).await?;
111                data::Scanner::try_from(vmo_tree)?.data()
112            }
113        })
114    }
115
116    pub fn metrics(&self) -> Result<Metrics, Error> {
117        Ok(data::Scanner::try_from(&self.vmo)?.metrics())
118    }
119}
120
121struct Connection {
122    fidl: validate::InspectPuppetProxy,
123    // Connection to Tree FIDL if Puppet supports it.
124    // Puppets can add support by implementing InitializeTree method.
125    root_link_channel: Option<fidl_inspect::TreeProxy>,
126}
127
128impl Connection {
129    async fn connect() -> Result<Self, Error> {
130        let puppet_fidl = fclient::connect_to_protocol::<validate::InspectPuppetMarker>().unwrap();
131        Ok(Self::new(puppet_fidl))
132    }
133
134    async fn get_config(&self) -> Result<Config, Error> {
135        let (printable_name, opts) = self.fidl.get_config().await?;
136        Ok(Config {
137            diff_type: opts.diff_type.into(),
138            printable_name,
139            has_runner_node: opts.has_runner_node.unwrap_or(false),
140            test_archive: true,
141        })
142    }
143
144    async fn fetch_link_channel(
145        fidl: &validate::InspectPuppetProxy,
146    ) -> Option<fidl_inspect::TreeProxy> {
147        let params =
148            validate::InitializationParams { vmo_size: Some(VMO_SIZE), ..Default::default() };
149        let response = fidl.initialize_tree(&params).await;
150        if let Ok((Some(tree_client_end), validate::TestResult::Ok)) = response {
151            Some(tree_client_end.into_proxy())
152        } else {
153            None
154        }
155    }
156
157    async fn get_vmo_handle(channel: &fidl_inspect::TreeProxy) -> Result<Vmo, Error> {
158        let tree_content = channel.get_content().await?;
159        let buffer = tree_content.buffer.ok_or(format_err!("Buffer doesn't contain VMO"))?;
160        Ok(buffer.vmo)
161    }
162
163    fn new(fidl: validate::InspectPuppetProxy) -> Self {
164        Self { fidl, root_link_channel: None }
165    }
166
167    async fn initialize_vmo(&mut self) -> Result<Vmo, Error> {
168        self.root_link_channel = Self::fetch_link_channel(&self.fidl).await;
169        match &self.root_link_channel {
170            Some(root_link_channel) => Self::get_vmo_handle(root_link_channel).await,
171            None => {
172                let params = validate::InitializationParams {
173                    vmo_size: Some(VMO_SIZE),
174                    ..Default::default()
175                };
176                let handle: Option<zx::Handle>;
177                let out = self.fidl.initialize(&params).await?;
178                if let (Some(out_handle), _) = out {
179                    handle = Some(out_handle);
180                } else {
181                    return Err(format_err!("Didn't get a VMO handle"));
182                }
183                match handle {
184                    Some(unwrapped_handle) => Ok(Vmo::from(unwrapped_handle)),
185                    None => Err(format_err!("Failed to unwrap handle")),
186                }
187            }
188        }
189    }
190}
191
192#[cfg(test)]
193pub(crate) mod tests {
194    use super::*;
195    use crate::create_node;
196    use anyhow::Context as _;
197    use fidl::endpoints::{create_proxy, RequestStream, ServerEnd};
198    use fidl_diagnostics_validate::{
199        Action, CreateNode, CreateNumericProperty, InspectPuppetMarker, InspectPuppetRequest,
200        InspectPuppetRequestStream, Options, TestResult, Value, ROOT_ID,
201    };
202    use fuchsia_async as fasync;
203    use fuchsia_inspect::{Inspector, InspectorConfig, IntProperty, Node};
204    use futures::prelude::*;
205    use log::info;
206    use std::collections::HashMap;
207    use zx::HandleBased;
208
209    #[fuchsia::test]
210    async fn test_fidl_loopback() -> Result<(), Error> {
211        let mut puppet = local_incomplete_puppet().await?;
212        assert_eq!(puppet.vmo.get_size().unwrap(), VMO_SIZE);
213        let tree = puppet.read_data().await?;
214        assert_eq!(tree.to_string(), "root ->".to_string());
215        let mut data = Data::new();
216        tree.compare(&data, DiffType::Full)?;
217        let mut action = create_node!(parent: ROOT_ID, id: 1, name: "child");
218        puppet.apply(&mut action).await?;
219        data.apply(&action)?;
220        let tree = data::Scanner::try_from(&puppet.vmo)?.data();
221        assert_eq!(tree.to_string(), "root ->\n> child ->".to_string());
222        tree.compare(&data, DiffType::Full)?;
223        Ok(())
224    }
225
226    // This is a partial implementation.
227    // All it can do is initialize, and then create nodes and int properties (which it
228    // will hold forever). Trying to create a uint property will return Unimplemented.
229    // Other actions will give various kinds of incorrect results.
230    pub(crate) async fn local_incomplete_puppet() -> Result<Puppet, Error> {
231        let (client_end, server_end) = create_proxy();
232        spawn_local_puppet(server_end).await;
233        Puppet::connect_local(client_end).await
234    }
235
236    async fn spawn_local_puppet(server_end: ServerEnd<InspectPuppetMarker>) {
237        fasync::Task::spawn(
238            async move {
239                // Inspector must be remembered so its VMO persists
240                let mut inspector_maybe: Option<Inspector> = None;
241                let mut nodes: HashMap<u32, Node> = HashMap::new();
242                let mut properties: HashMap<u32, IntProperty> = HashMap::new();
243                let server_chan = fasync::Channel::from_channel(server_end.into_channel());
244                let mut stream = InspectPuppetRequestStream::from_channel(server_chan);
245                while let Some(event) = stream.try_next().await? {
246                    match event {
247                        InspectPuppetRequest::GetConfig { responder } => {
248                            responder.send("*Local*", Options::default()).ok();
249                        }
250                        InspectPuppetRequest::Initialize { params, responder } => {
251                            let inspector = match params.vmo_size {
252                                Some(size) => {
253                                    Inspector::new(InspectorConfig::default().size(size as usize))
254                                }
255                                None => Inspector::default(),
256                            };
257                            responder
258                                .send(
259                                    inspector.duplicate_vmo().map(|v| v.into_handle()),
260                                    TestResult::Ok,
261                                )
262                                .context("responding to initialize")?;
263                            inspector_maybe = Some(inspector);
264                        }
265                        InspectPuppetRequest::Act { action, responder } => match action {
266                            Action::CreateNode(CreateNode { parent, id, name }) => {
267                                if let Some(ref inspector) = inspector_maybe {
268                                    let parent_node = if parent == ROOT_ID {
269                                        inspector.root()
270                                    } else {
271                                        nodes.get(&parent).unwrap()
272                                    };
273                                    let new_child = parent_node.create_child(name);
274                                    nodes.insert(id, new_child);
275                                }
276                                responder.send(TestResult::Ok)?;
277                            }
278                            Action::CreateNumericProperty(CreateNumericProperty {
279                                parent,
280                                id,
281                                name,
282                                value: Value::IntT(value),
283                            }) => {
284                                inspector_maybe.as_ref().map(|i| {
285                                    let parent_node = if parent == 0 {
286                                        i.root()
287                                    } else {
288                                        nodes.get(&parent).unwrap()
289                                    };
290                                    properties.insert(id, parent_node.create_int(name, value))
291                                });
292                                responder.send(TestResult::Ok)?;
293                            }
294                            Action::CreateNumericProperty(CreateNumericProperty {
295                                value: Value::UintT(_),
296                                ..
297                            }) => {
298                                responder.send(TestResult::Unimplemented)?;
299                            }
300
301                            _ => responder.send(TestResult::Illegal)?,
302                        },
303                        InspectPuppetRequest::InitializeTree { params: _, responder } => {
304                            responder.send(None, TestResult::Unimplemented)?;
305                        }
306                        InspectPuppetRequest::ActLazy { lazy_action: _, responder } => {
307                            responder.send(TestResult::Unimplemented)?;
308                        }
309                        InspectPuppetRequest::Publish { responder } => {
310                            responder.send(TestResult::Unimplemented)?;
311                        }
312                        InspectPuppetRequest::_UnknownMethod { .. } => {}
313                    }
314                }
315                Ok(())
316            }
317            .unwrap_or_else(|e: anyhow::Error| info!("error running validate interface: {:?}", e)),
318        )
319        .detach();
320    }
321}