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::PUPPET_MONIKER;
6use super::data::{self, Data, LazyNode};
7use super::metrics::Metrics;
8use anyhow::{Error, format_err};
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 act_lazy_thread_local(
73        &mut self,
74        lazy_action: &mut validate::LazyAction,
75    ) -> Result<validate::TestResult, Error> {
76        Ok(self.connection.fidl.act_lazy_thread_local(lazy_action).await?)
77    }
78
79    pub async fn publish(&mut self) -> Result<validate::TestResult, Error> {
80        Ok(self.connection.fidl.publish().await?)
81    }
82
83    pub async fn connect() -> Result<Self, Error> {
84        Puppet::initialize_with_connection(Connection::connect().await?).await
85    }
86
87    pub(crate) async fn shutdown(self) {
88        let lifecycle_controller =
89            fclient::connect_to_protocol::<fidl_fuchsia_sys2::LifecycleControllerMarker>().unwrap();
90        lifecycle_controller.stop_instance(&format!("./{PUPPET_MONIKER}")).await.unwrap().unwrap();
91    }
92
93    /// Get the printable name associated with this puppet/test
94    pub fn printable_name(&self) -> &str {
95        &self.config.printable_name
96    }
97
98    #[cfg(test)]
99    pub async fn connect_local(local_fidl: validate::InspectPuppetProxy) -> Result<Puppet, Error> {
100        let mut puppet = Puppet::initialize_with_connection(Connection::new(local_fidl)).await?;
101        puppet.config.test_archive = false;
102        Ok(puppet)
103    }
104
105    async fn initialize_with_connection(mut connection: Connection) -> Result<Puppet, Error> {
106        Ok(Puppet {
107            vmo: connection.initialize_vmo().await?,
108            config: connection.get_config().await?,
109            connection,
110        })
111    }
112
113    pub async fn read_data(&self) -> Result<Data, Error> {
114        Ok(match &self.connection.root_link_channel {
115            None => data::Scanner::try_from(&self.vmo)?.data(),
116            Some(root_link_channel) => {
117                let vmo_tree = LazyNode::new(root_link_channel.clone()).await?;
118                data::Scanner::try_from(vmo_tree)?.data()
119            }
120        })
121    }
122
123    pub fn metrics(&self) -> Result<Metrics, Error> {
124        Ok(data::Scanner::try_from(&self.vmo)?.metrics())
125    }
126}
127
128struct Connection {
129    fidl: validate::InspectPuppetProxy,
130    // Connection to Tree FIDL if Puppet supports it.
131    // Puppets can add support by implementing InitializeTree method.
132    root_link_channel: Option<fidl_inspect::TreeProxy>,
133}
134
135impl Connection {
136    async fn connect() -> Result<Self, Error> {
137        let puppet_fidl = fclient::connect_to_protocol::<validate::InspectPuppetMarker>().unwrap();
138        Ok(Self::new(puppet_fidl))
139    }
140
141    async fn get_config(&self) -> Result<Config, Error> {
142        let (printable_name, opts) = self.fidl.get_config().await?;
143        Ok(Config {
144            diff_type: opts.diff_type.into(),
145            printable_name,
146            has_runner_node: opts.has_runner_node.unwrap_or(false),
147            test_archive: true,
148        })
149    }
150
151    async fn fetch_link_channel(
152        fidl: &validate::InspectPuppetProxy,
153    ) -> Option<fidl_inspect::TreeProxy> {
154        let params =
155            validate::InitializationParams { vmo_size: Some(VMO_SIZE), ..Default::default() };
156        let response = fidl.initialize_tree(&params).await;
157        if let Ok((Some(tree_client_end), validate::TestResult::Ok)) = response {
158            Some(tree_client_end.into_proxy())
159        } else {
160            None
161        }
162    }
163
164    async fn get_vmo_handle(channel: &fidl_inspect::TreeProxy) -> Result<Vmo, Error> {
165        let tree_content = channel.get_content().await?;
166        let buffer =
167            tree_content.buffer.ok_or_else(|| format_err!("Buffer doesn't contain VMO"))?;
168        Ok(buffer.vmo)
169    }
170
171    fn new(fidl: validate::InspectPuppetProxy) -> Self {
172        Self { fidl, root_link_channel: None }
173    }
174
175    async fn initialize_vmo(&mut self) -> Result<Vmo, Error> {
176        self.root_link_channel = Self::fetch_link_channel(&self.fidl).await;
177        match &self.root_link_channel {
178            Some(root_link_channel) => Self::get_vmo_handle(root_link_channel).await,
179            None => {
180                let params = validate::InitializationParams {
181                    vmo_size: Some(VMO_SIZE),
182                    ..Default::default()
183                };
184                let handle: Option<zx::Handle>;
185                let out = self.fidl.initialize(&params).await?;
186                if let (Some(out_handle), _) = out {
187                    handle = Some(out_handle);
188                } else {
189                    return Err(format_err!("Didn't get a VMO handle"));
190                }
191                match handle {
192                    Some(unwrapped_handle) => Ok(Vmo::from(unwrapped_handle)),
193                    None => Err(format_err!("Failed to unwrap handle")),
194                }
195            }
196        }
197    }
198}
199
200#[cfg(test)]
201pub(crate) mod tests {
202    use super::*;
203    use crate::create_node;
204    use anyhow::Context as _;
205    use fidl::endpoints::{RequestStream, ServerEnd, create_proxy};
206    use fidl_diagnostics_validate::{
207        Action, CreateNode, CreateNumericProperty, InspectPuppetMarker, InspectPuppetRequest,
208        InspectPuppetRequestStream, Options, ROOT_ID, TestResult, Value,
209    };
210    use fuchsia_async as fasync;
211    use fuchsia_inspect::{Inspector, InspectorConfig, IntProperty, Node};
212    use futures::prelude::*;
213    use log::info;
214    use std::collections::HashMap;
215    use zx::HandleBased;
216
217    #[fuchsia::test]
218    async fn test_fidl_loopback() -> Result<(), Error> {
219        let mut puppet = local_incomplete_puppet().await?;
220        assert_eq!(puppet.vmo.get_size().unwrap(), VMO_SIZE);
221        let tree = puppet.read_data().await?;
222        assert_eq!(tree.to_string(), "root ->".to_string());
223        let mut data = Data::new();
224        tree.compare(&data, DiffType::Full)?;
225        let mut action = create_node!(parent: ROOT_ID, id: 1, name: "child");
226        puppet.apply(&mut action).await?;
227        data.apply(&action)?;
228        let tree = data::Scanner::try_from(&puppet.vmo)?.data();
229        assert_eq!(tree.to_string(), "root ->\n> child ->".to_string());
230        tree.compare(&data, DiffType::Full)?;
231        Ok(())
232    }
233
234    // This is a partial implementation.
235    // All it can do is initialize, and then create nodes and int properties (which it
236    // will hold forever). Trying to create a uint property will return Unimplemented.
237    // Other actions will give various kinds of incorrect results.
238    pub(crate) async fn local_incomplete_puppet() -> Result<Puppet, Error> {
239        let (client_end, server_end) = create_proxy();
240        spawn_local_puppet(server_end).await;
241        Puppet::connect_local(client_end).await
242    }
243
244    async fn spawn_local_puppet(server_end: ServerEnd<InspectPuppetMarker>) {
245        fasync::Task::spawn(
246            async move {
247                // Inspector must be remembered so its VMO persists
248                let mut inspector_maybe: Option<Inspector> = None;
249                let mut nodes: HashMap<u32, Node> = HashMap::new();
250                let mut properties: HashMap<u32, IntProperty> = HashMap::new();
251                let server_chan = fasync::Channel::from_channel(server_end.into_channel());
252                let mut stream = InspectPuppetRequestStream::from_channel(server_chan);
253                while let Some(event) = stream.try_next().await? {
254                    match event {
255                        InspectPuppetRequest::GetConfig { responder } => {
256                            responder.send("*Local*", Options::default()).ok();
257                        }
258                        InspectPuppetRequest::Initialize { params, responder } => {
259                            let inspector = match params.vmo_size {
260                                Some(size) => {
261                                    Inspector::new(InspectorConfig::default().size(size as usize))
262                                }
263                                None => Inspector::default(),
264                            };
265                            responder
266                                .send(
267                                    inspector.duplicate_vmo().map(|v| v.into_handle()),
268                                    TestResult::Ok,
269                                )
270                                .context("responding to initialize")?;
271                            inspector_maybe = Some(inspector);
272                        }
273                        InspectPuppetRequest::Act { action, responder } => match action {
274                            Action::CreateNode(CreateNode { parent, id, name }) => {
275                                if let Some(ref inspector) = inspector_maybe {
276                                    let parent_node = if parent == ROOT_ID {
277                                        inspector.root()
278                                    } else {
279                                        nodes.get(&parent).unwrap()
280                                    };
281                                    let new_child = parent_node.create_child(name);
282                                    nodes.insert(id, new_child);
283                                }
284                                responder.send(TestResult::Ok)?;
285                            }
286                            Action::CreateNumericProperty(CreateNumericProperty {
287                                parent,
288                                id,
289                                name,
290                                value: Value::IntT(value),
291                            }) => {
292                                inspector_maybe.as_ref().map(|i| {
293                                    let parent_node = if parent == 0 {
294                                        i.root()
295                                    } else {
296                                        nodes.get(&parent).unwrap()
297                                    };
298                                    properties.insert(id, parent_node.create_int(name, value))
299                                });
300                                responder.send(TestResult::Ok)?;
301                            }
302                            Action::CreateNumericProperty(CreateNumericProperty {
303                                value: Value::UintT(_),
304                                ..
305                            }) => {
306                                responder.send(TestResult::Unimplemented)?;
307                            }
308
309                            _ => responder.send(TestResult::Illegal)?,
310                        },
311                        InspectPuppetRequest::InitializeTree { params: _, responder } => {
312                            responder.send(None, TestResult::Unimplemented)?;
313                        }
314                        InspectPuppetRequest::ActLazy { lazy_action: _, responder } => {
315                            responder.send(TestResult::Unimplemented)?;
316                        }
317                        InspectPuppetRequest::Publish { responder } => {
318                            responder.send(TestResult::Unimplemented)?;
319                        }
320                        InspectPuppetRequest::ActLazyThreadLocal { responder, .. } => {
321                            responder.send(TestResult::Unimplemented)?;
322                        }
323                        InspectPuppetRequest::_UnknownMethod { .. } => {}
324                    }
325                }
326                Ok(())
327            }
328            .unwrap_or_else(|e: anyhow::Error| info!("error running validate interface: {:?}", e)),
329        )
330        .detach();
331    }
332}