Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,43 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {

To cancel an *async query job* instead, use `cancel_query()` — see [Async query jobs](#async-query-jobs-and-dataset-refresh) above.

### Runtime health and status

`is_ready()` is a single boolean for the whole runtime. When you need to know *which*
component is not ready, `runtime_status()` reports each connection separately. Both use
the HTTP API, so configure `http_url()`.

```rust,no_run
use spiceai::ClientBuilder;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = ClientBuilder::new()
.http_url("http://localhost:8090")
.build()
.await?;

if !client.is_ready().await? {
println!("runtime is not ready yet");
}

for component in client.runtime_status().await? {
println!("{} ({}): {}", component.name, component.endpoint, component.status);
}
// http (127.0.0.1:8090): Ready
// flight (127.0.0.1:50051): Ready
// metrics (N/A): Disabled
// opentelemetry (127.0.0.1:50051): Ready

Ok(())
}
```

Each `ConnectionDetails` carries the component `name` (`http`, `flight`, `metrics` or
`opentelemetry`), its `endpoint`, and its `status` — a `ComponentStatus` of `Initializing`,
`Ready`, `Disabled`, `Error`, `Refreshing`, `ShuttingDown` or `NotLoaded`. A status a
future runtime adds deserializes into `ComponentStatus::Other` rather than failing.

## Documentation

Check out our [Documentation](https://docs.spice.ai/sdks/rust-sdk) to learn more about how to use the Rust SDK.
48 changes: 48 additions & 0 deletions src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::{
config::{GenericError, SPICE_CLOUD_FLIGHT_ADDR, SPICE_LOCAL_FLIGHT_ADDR},
dataset::{DatasetError, DatasetRefreshRequest, DatasetRefreshResponse},
flight::{SqlFlightClient, is_connection_reset_generic_error},
status::{ConnectionDetails, StatusError},
tls::{FlightChannelBuilder, ensure_crypto_provider, new_tls_flight_channel},
};
use arrow::record_batch::RecordBatch;
Expand Down Expand Up @@ -677,6 +678,53 @@ impl SpiceClient {

http_client.refresh_dataset(dataset_name, &request).await
}

/// Returns the status of each runtime connection.
///
/// Backed by `GET /v1/status`. Where [`is_ready`](Self::is_ready) reports a single
/// boolean for the whole runtime, this reports `http`, `flight`, `metrics` and
/// `opentelemetry` individually, so it can say *which* component is not ready.
///
/// **Note:** Requires [`http_url()`](SpiceClientBuilder::http_url) to be configured.
///
/// ```no_run
/// # use spiceai::ClientBuilder;
/// # #[tokio::main]
/// # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
/// let client = ClientBuilder::new()
/// .http_url("http://localhost:8090")
/// .build()
/// .await?;
///
/// for component in client.runtime_status().await? {
/// println!("{} ({}): {}", component.name, component.endpoint, component.status);
/// }
/// # Ok(())
/// # }
/// ```
pub async fn runtime_status(&self) -> Result<Vec<ConnectionDetails>, StatusError> {
let http_client = self
.http_client
.as_ref()
.ok_or(StatusError::HttpNotConfigured)?;

http_client.runtime_status().await
}

/// Returns whether the runtime is ready to serve queries.
///
/// Backed by `GET /v1/ready`. Returns `Ok(false)` when the runtime responds that it
/// is not ready; an `Err` means the probe itself could not be completed.
///
/// **Note:** Requires [`http_url()`](SpiceClientBuilder::http_url) to be configured.
pub async fn is_ready(&self) -> Result<bool, StatusError> {
let http_client = self
.http_client
.as_ref()
.ok_or(StatusError::HttpNotConfigured)?;

http_client.is_ready().await
}
}

/// Builder for creating a `SpiceClient`.
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod flight;
mod params;
pub mod query;
mod redirect;
pub mod status;
pub mod tls;
mod util;

Expand All @@ -24,6 +25,7 @@ pub use query::{
QueryError, QueryInfo, QueryJob, QueryListResponse, QueryResult, QueryResultStream,
QueryStatus, QuerySubmitOptions, QuerySummary,
};
pub use status::{ComponentStatus, ConnectionDetails, StatusError};

// Further public exports and integrations
pub use futures::StreamExt;
15 changes: 15 additions & 0 deletions src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,21 @@ impl QueryHttpClient {
}
}

/// The underlying reqwest client, for request builders constructed in sibling modules.
pub(crate) fn client(&self) -> &reqwest::Client {
&self.client
}

/// The runtime's HTTP base URL, with any trailing slash already trimmed.
pub(crate) fn base_url(&self) -> &str {
&self.base_url
}

/// Applies the configured API key to a request, if one is set.
pub(crate) fn authorized(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
self.add_auth(req)
}

fn add_auth(&self, req: reqwest::RequestBuilder) -> reqwest::RequestBuilder {
match &self.api_key {
Some(key) => req.header("X-API-Key", key),
Expand Down
Loading
Loading