diff --git a/proto/holo.proto b/proto/holo.proto index 4af9a94..de334ca 100644 --- a/proto/holo.proto +++ b/proto/holo.proto @@ -132,6 +132,13 @@ message GetRequest { // Target YANG path. Path path = 4; + + // YANG paths to exclude from the response (matched at any depth). + // A single-element path with just a name (optionally module-qualified + // as "module:name") matches any node with that name. Multi-element + // paths match a specific subtree wherever it occurs. + // Applies only to state data. + repeated Path exclude = 5; } message GetResponse { @@ -313,6 +320,11 @@ enum SchemaFormat { message Path { // Ordered list of elements forming the path. repeated PathElem elem = 1; + + // Maximum depth of the YANG tree traversal beyond the requested path + // (0 = unlimited). A value of 1 returns only immediate children of + // the target. Applies only to state data. + uint32 max_depth = 2; } // A single element within a YANG data path. diff --git a/src/grpc.rs b/src/grpc.rs index e77f8a7..9649160 100644 --- a/src/grpc.rs +++ b/src/grpc.rs @@ -101,14 +101,30 @@ impl GrpcClient { format: DataFormat, with_defaults: bool, xpath: Option, + max_depth: u32, + exclude: &[String], ) -> Result { - let path = xpath.map(|x| proto::Path::from_xpath(&x)); + let path = match xpath { + Some(x) => { + let mut p = proto::Path::from_xpath(&x); + p.max_depth = max_depth; + Some(p) + } + None if max_depth > 0 => Some(proto::Path { + elem: Vec::new(), + max_depth, + }), + None => None, + }; + let exclude = + exclude.iter().map(|s| proto::Path::from_xpath(s)).collect(); let data = self .rpc_sync_get(proto::GetRequest { r#type: data_type as i32, encoding: proto::Encoding::from(format) as i32, with_defaults, path, + exclude, }) .map_err(Error::Backend)? .into_inner() @@ -266,7 +282,8 @@ impl proto::Path { Some(pos) => { let name = &segment[..pos]; let mut keys = HashMap::new(); - for kv in segment[pos..].split('[').filter(|s| !s.is_empty()) + for kv in + segment[pos..].split('[').filter(|s| !s.is_empty()) { let kv = kv.trim_end_matches(']'); if let Some(eq_pos) = kv.find('=') { @@ -287,7 +304,10 @@ impl proto::Path { } }) .collect(); - proto::Path { elem: elems } + proto::Path { + elem: elems, + max_depth: 0, + } } } diff --git a/src/internal_commands.rs b/src/internal_commands.rs index 7be2056..853f314 100644 --- a/src/internal_commands.rs +++ b/src/internal_commands.rs @@ -28,10 +28,16 @@ const XPATH_PROTOCOL: &str = "/ietf-routing:routing/control-plane-protocols/control-plane-protocol"; const XPATH_RIB: &str = "/ietf-routing:routing/ribs/rib"; +/// Excluded from all YangTableBuilder queries by default for performance — +/// BGP RIBs are large sub-trees that slow down unrelated show commands. +const DEFAULT_EXCLUDES: &[&str] = &["ietf-bgp:rib"]; + struct YangTableBuilder<'a> { session: &'a mut Session, data_type: proto::get_request::DataType, paths: Vec<(String, Vec)>, + max_depth: u32, + exclude: Vec, } struct YangTableColumn { @@ -62,6 +68,8 @@ impl<'a> YangTableBuilder<'a> { session, data_type, paths: Vec::new(), + max_depth: 0, + exclude: DEFAULT_EXCLUDES.iter().map(|s| s.to_string()).collect(), } } @@ -71,6 +79,20 @@ impl<'a> YangTableBuilder<'a> { self } + // Sets the maximum depth for the fetch operation. + #[allow(dead_code)] + pub fn max_depth(mut self, depth: u32) -> Self { + self.max_depth = depth; + self + } + + // Adds a YANG node name to the exclude list. + #[allow(dead_code)] + pub fn exclude(mut self, name: &str) -> Self { + self.exclude.push(name.to_string()); + self + } + // Adds a YANG list key filter to the last added XPath in the builder. pub fn filter_list_key(mut self, key: &str, value: Option) -> Self where @@ -202,7 +224,13 @@ impl<'a> YangTableBuilder<'a> { let xpath_req = "/ietf-routing:routing/control-plane-protocols"; // Fetch data. - let data = fetch_data(self.session, self.data_type, xpath_req)?; + let data = fetch_data_filtered( + self.session, + self.data_type, + xpath_req, + self.max_depth, + &self.exclude, + )?; let Some(dnode) = data.reference() else { return Ok(()); }; @@ -258,15 +286,24 @@ fn write_output( Ok(()) } -fn fetch_data( +fn fetch_data_filtered( session: &mut Session, data_type: proto::get_request::DataType, xpath: &str, + max_depth: u32, + exclude: &[String], ) -> Result, String> { let yang_ctx = YANG_CTX.get().unwrap(); let data_format = DataFormat::LYB; let data = session - .get(data_type, data_format, true, Some(xpath.to_owned())) + .get( + data_type, + data_format, + true, + Some(xpath.to_owned()), + max_depth, + exclude, + ) .map_err(|error| format!("% failed to fetch state data: {}", error))?; DataTree::parse_string( yang_ctx, @@ -278,6 +315,15 @@ fn fetch_data( .map_err(|error| format!("% failed to parse data: {}", error)) } +#[inline] +fn fetch_data( + session: &mut Session, + data_type: proto::get_request::DataType, + xpath: &str, +) -> Result, String> { + fetch_data_filtered(session, data_type, xpath, 0, &[]) +} + // ===== impl DataNodeRef ===== /// Extension methods for DataNodeRef. @@ -638,6 +684,12 @@ pub fn cmd_show_state( ) -> Result { let xpath = get_opt_arg(&mut args, "xpath"); let format = get_opt_arg(&mut args, "format"); + let max_depth = get_opt_arg(&mut args, "depth") + .and_then(|d| d.parse::().ok()) + .unwrap_or_default(); + let exclude: Vec = get_opt_arg(&mut args, "exclude") + .map(|s| s.split(',').map(str::to_string).collect()) + .unwrap_or_default(); let format = match format.as_deref() { Some("json") => DataFormat::JSON, Some("xml") => DataFormat::XML, @@ -645,8 +697,14 @@ pub fn cmd_show_state( None => DataFormat::JSON, }; - match session.get(proto::get_request::DataType::State, format, false, xpath) - { + match session.get( + proto::get_request::DataType::State, + format, + false, + xpath, + max_depth, + &exclude, + ) { Ok(proto::data_tree::Data::DataString(data)) => { write_output(session, &data)?; } diff --git a/src/internal_commands.xml b/src/internal_commands.xml index 7b58935..6193860 100644 --- a/src/internal_commands.xml +++ b/src/internal_commands.xml @@ -18,10 +18,16 @@ - + - + + + + + + + @@ -29,6 +35,12 @@ + + + + + + diff --git a/src/session.rs b/src/session.rs index a45f6a6..4825bd3 100644 --- a/src/session.rs +++ b/src/session.rs @@ -62,6 +62,8 @@ impl Session { data_format, false, None, + 0, + &[], ) .unwrap(); let running = DataTree::parse_string( @@ -314,9 +316,17 @@ impl Session { format: DataFormat, with_defaults: bool, xpath: Option, + max_depth: u32, + exclude: &[String], ) -> Result { - self.grpc_client - .get(data_type, format, with_defaults, xpath) + self.grpc_client.get( + data_type, + format, + with_defaults, + xpath, + max_depth, + exclude, + ) } pub fn execute(