From ffa4a67bcdfcf85b35cd1da4486fba13d81b0636 Mon Sep 17 00:00:00 2001 From: Renato Westphal Date: Sat, 12 Oct 2024 14:36:46 -0300 Subject: [PATCH] daemon-grpc: introduce the `GetSchema` RPC This RPC mirrors NETCONF's get-schema operation but offers more flexibility, as it can fetch YANG submodules in addition to YANG modules. The primary use case is for a CLI program to retrieve the list of supported YANG modules at startup, then use this RPC to fetch any missing modules. Signed-off-by: Renato Westphal --- holo-daemon/src/northbound/client/grpc.rs | 77 +++++++++++++++++++++++ proto/holo.proto | 37 +++++++++++ 2 files changed, 114 insertions(+) diff --git a/holo-daemon/src/northbound/client/grpc.rs b/holo-daemon/src/northbound/client/grpc.rs index 4ecbb8b5..19ddce96 100644 --- a/holo-daemon/src/northbound/client/grpc.rs +++ b/holo-daemon/src/northbound/client/grpc.rs @@ -18,6 +18,7 @@ use yang3::data::{ Data, DataDiff, DataFormat, DataOperation, DataParserFlags, DataPrinterFlags, DataTree, DataValidationFlags, }; +use yang3::schema::{SchemaOutputFormat, SchemaPrinterFlags}; use crate::northbound::client::api; use crate::{config, northbound}; @@ -88,6 +89,65 @@ impl proto::Northbound for NorthboundService { Ok(Response::new(reply)) } + async fn get_schema( + &self, + grpc_request: Request, + ) -> Result, Status> { + let grpc_request = grpc_request.into_inner(); + debug_span!("northbound").in_scope(|| { + debug_span!("client", name = "grpc").in_scope(|| { + debug!("received GetSchema() request"); + trace!("{:?}", grpc_request); + }); + }); + + // Lookup schema module. + let yang_ctx = YANG_CTX.get().unwrap(); + let module_name = grpc_request.module_name; + let module_rev = get_optional_string(grpc_request.module_revision); + let submodule_name = get_optional_string(grpc_request.submodule_name); + let submodule_rev = + get_optional_string(grpc_request.submodule_revision); + let format = proto::SchemaFormat::try_from(grpc_request.format) + .map_err(|_| Status::invalid_argument("Invalid schema format"))?; + + // Get module. + let module = match module_rev { + Some(module_rev) => { + yang_ctx.get_module(&module_name, Some(&module_rev)) + } + None => yang_ctx.get_module_latest(&module_name), + } + .ok_or_else(|| Status::not_found("YANG module not found"))?; + + let data = match submodule_name { + Some(submodule_name) => { + // Get submodule. + let submodule = match submodule_rev { + Some(submodule_rev) => module + .get_submodule(&submodule_name, Some(&submodule_rev)), + None => module.get_submodule_latest(&submodule_name), + } + .ok_or_else(|| Status::not_found("YANG submodule not found"))?; + + // Print submodule data based on the requested format. + submodule + .print_string(format.into(), SchemaPrinterFlags::empty()) + .expect("Failed to print YANG submodule") + } + None => { + // Print module data based on the requested format. + module + .print_string(format.into(), SchemaPrinterFlags::empty()) + .expect("Failed to print YANG module") + } + }; + + // Return schema data to the gRPC client. + let grpc_response = proto::GetSchemaResponse { data }; + Ok(Response::new(grpc_response)) + } + async fn get( &self, grpc_request: Request, @@ -400,6 +460,15 @@ impl From for DataFormat { } } +impl From for SchemaOutputFormat { + fn from(format: proto::SchemaFormat) -> SchemaOutputFormat { + match format { + proto::SchemaFormat::Yang => SchemaOutputFormat::YANG, + proto::SchemaFormat::Yin => SchemaOutputFormat::YIN, + } + } +} + impl TryFrom for api::DataType { type Error = Status; @@ -424,6 +493,14 @@ fn get_timestamp() -> i64 { .as_secs() as i64 } +fn get_optional_string(data: String) -> Option { + if data.is_empty() { + None + } else { + Some(data) + } +} + fn data_tree_init( dtree: &DataTree<'static>, encoding: proto::Encoding, diff --git a/proto/holo.proto b/proto/holo.proto index 1fb4af72..de0308e6 100644 --- a/proto/holo.proto +++ b/proto/holo.proto @@ -29,6 +29,9 @@ service Northbound { // Retrieve the capabilities supported by the target. rpc Capabilities(CapabilitiesRequest) returns (CapabilitiesResponse) {} + // Retrieve the specified schema data from the target. + rpc GetSchema(GetSchemaRequest) returns (GetSchemaResponse) {} + // Retrieve configuration data, state data or both from the target. rpc Get(GetRequest) returns (GetResponse) {} @@ -71,6 +74,34 @@ message CapabilitiesResponse { repeated Encoding supported_encodings = 3; } +// +// RPC: GetSchema() +// +message GetSchemaRequest { + // The name of the module to retrieve. + string module_name = 1; + + // The specific revision of the module (optional). + string module_revision = 2; + + // The name of the submodule to retrieve (optional). + string submodule_name = 3; + + // The specific revision of the submodule (optional). + string submodule_revision = 4; + + // The desired format of the schema output (optional). + SchemaFormat format = 5; +} + +message GetSchemaResponse { + // - grpc::StatusCode::OK: Success. + // - grpc::StatusCode::NOT_FOUND: Schema module not found. + + // The requested schema data. + string data = 1; +} + // // RPC: Get() // @@ -243,6 +274,12 @@ enum Encoding { LYB = 2; } +// Supported schema formats. +enum SchemaFormat { + YANG = 0; + YIN = 1; +} + // YANG instance data. message DataTree { // The encoding format of the data.