Skip to content

Commit

Permalink
daemon-grpc: introduce the Validate() RPC
Browse files Browse the repository at this point in the history
This RPC enables users to fully validate a candidate configuration
without committing it.

Signed-off-by: Renato Westphal <[email protected]>
  • Loading branch information
rwestphal committed May 9, 2023
1 parent f472e95 commit 5bb16e0
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 0 deletions.
12 changes: 12 additions & 0 deletions holo-daemon/src/northbound/client/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub mod client {
pub enum Request {
// Request to get data (configuration, state or both).
Get(GetRequest),
// Request to validate a candidate configuration.
Validate(ValidateRequest),
// Request to change the running configuration.
Commit(CommitRequest),
// Request to invoke a YANG RPC or Action.
Expand All @@ -45,6 +47,15 @@ pub mod client {
pub dtree: DataTree,
}

#[derive(Debug)]
pub struct ValidateRequest {
pub config: DataTree,
pub responder: Responder<Result<ValidateResponse>>,
}

#[derive(Debug)]
pub struct ValidateResponse {}

#[derive(Debug)]
pub struct CommitRequest {
pub operation: CommitOperation,
Expand Down Expand Up @@ -97,6 +108,7 @@ pub mod client {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Request::Get(_) => write!(f, "Get"),
Request::Validate(_) => write!(f, "Validate"),
Request::Commit(_) => write!(f, "Commit"),
Request::Execute(_) => write!(f, "Execute"),
Request::ListTransactions(_) => write!(f, "ListTransactions"),
Expand Down
45 changes: 45 additions & 0 deletions holo-daemon/src/northbound/client/grpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,51 @@ impl proto::Northbound for NorthboundService {
Ok(Response::new(grpc_response))
}

async fn validate(
&self,
grpc_request: Request<proto::ValidateRequest>,
) -> Result<Response<proto::ValidateResponse>, Status> {
let yang_ctx = YANG_CTX.get().unwrap();
let grpc_request = grpc_request.into_inner();
debug_span!("northbound").in_scope(|| {
debug_span!("client", name = "grpc").in_scope(|| {
debug!("received Validate() request");
trace!("{:?}", grpc_request);
});
});

// Create oneshot channel to receive response back from the northbound.
let (responder_tx, responder_rx) = oneshot::channel();

// Convert and relay gRPC request to the northbound.
let config_tree = grpc_request.config.ok_or_else(|| {
Status::invalid_argument("Missing 'config' field")
})?;
let encoding = proto::Encoding::from_i32(config_tree.encoding)
.ok_or_else(|| Status::invalid_argument("Invalid data encoding"))?;
let config = DataTree::parse_string(
yang_ctx,
&config_tree.data,
DataFormat::from(encoding),
DataParserFlags::empty(),
DataValidationFlags::NO_STATE,
)
.map_err(|error| Status::invalid_argument(error.to_string()))?;
let nb_request =
api::client::Request::Validate(api::client::ValidateRequest {
config,
responder: responder_tx,
});
self.request_tx.send(nb_request).await.unwrap();

// Receive response from the northbound.
let _nb_response = responder_rx.await.unwrap()?;

// Prepare and send response to the gRPC client.
let grpc_response = proto::ValidateResponse {};
Ok(Response::new(grpc_response))
}

async fn commit(
&self,
grpc_request: Request<proto::CommitRequest>,
Expand Down
23 changes: 23 additions & 0 deletions holo-daemon/src/northbound/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ impl Northbound {
.await;
let _ = request.responder.send(response);
}
capi::client::Request::Validate(request) => {
let response =
self.process_client_validate(request.config).await;
if let Err(error) = &response {
warn!(%error, "configuration validation failed");
}
let _ = request.responder.send(response);
}
capi::client::Request::Commit(request) => {
let response = self
.process_client_commit(
Expand Down Expand Up @@ -230,6 +238,21 @@ impl Northbound {
Ok(capi::client::GetResponse { dtree })
}

// Processes a `Validate` message received from an external client.
async fn process_client_validate(
&mut self,
candidate: DataTree,
) -> Result<capi::client::ValidateResponse> {
let candidate = Arc::new(candidate);

// Validate the candidate configuration.
self.validate_notify(&candidate)
.await
.map_err(Error::TransactionValidation)?;

Ok(capi::client::ValidateResponse {})
}

// Processes a `Commit` message received from an external client.
async fn process_client_commit(
&mut self,
Expand Down
17 changes: 17 additions & 0 deletions proto/holo.proto
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ service Northbound {
// Retrieve configuration data, state data or both from the target.
rpc Get(GetRequest) returns (GetResponse) {}

// Validate the configuration without committing it.
rpc Validate(ValidateRequest) returns (ValidateResponse) {}

// Create a new configuration transaction using a two-phase commit protocol.
rpc Commit(CommitRequest) returns (CommitResponse) {}

Expand Down Expand Up @@ -106,6 +109,20 @@ message GetResponse {
DataTree data = 2;
}

//
// RPC: Validate()
//
message ValidateRequest {
// Configuration data.
DataTree config = 1;
}

message ValidateResponse {
// Return values:
// - grpc::StatusCode::OK: Success.
// - grpc::StatusCode::INVALID_ARGUMENT: Validation error.
}

//
// RPC: Commit()
//
Expand Down

0 comments on commit 5bb16e0

Please sign in to comment.