use boxlite_shared::{
BlockDeviceSource, BoxliteError, BoxliteResult, Filesystem, GuestClient, GuestInitRequest,
NetworkInit, PingRequest, QuiesceRequest, ShutdownRequest, ThawRequest, VirtiofsSource, Volume,
guest_init_response,
};
use tonic::transport::Channel;
pub struct GuestInterface {
client: GuestClient<Channel>,
}
impl GuestInterface {
pub fn new(channel: Channel) -> Self {
Self {
client: GuestClient::new(channel),
}
}
pub async fn init(&mut self, config: GuestInitConfig) -> BoxliteResult<()> {
tracing::debug!("Sending GuestInit request");
tracing::trace!(
volumes = config.volumes.len(),
network = ?config.network,
"Guest init configuration"
);
let request = GuestInitRequest {
volumes: config.volumes.into_iter().map(|v| v.into_proto()).collect(),
network: config.network.map(|n| NetworkInit {
interface: n.interface,
ip: n.ip,
gateway: n.gateway,
}),
};
let response = self.client.init(request).await?.into_inner();
match response.result {
Some(guest_init_response::Result::Success(_)) => {
tracing::debug!("Guest initialized");
Ok(())
}
Some(guest_init_response::Result::Error(err)) => {
tracing::error!("Guest init failed: {}", err.reason);
Err(BoxliteError::Internal(format!(
"Guest init failed: {}",
err.reason
)))
}
None => Err(BoxliteError::Internal(
"GuestInit response missing result".to_string(),
)),
}
}
#[allow(dead_code)] pub async fn ping(&mut self) -> BoxliteResult<()> {
let _response = self.client.ping(PingRequest {}).await?;
Ok(())
}
pub async fn shutdown(&mut self) -> BoxliteResult<()> {
let _response = self.client.shutdown(ShutdownRequest {}).await?;
Ok(())
}
pub async fn quiesce(&mut self) -> BoxliteResult<u32> {
let response = self.client.quiesce(QuiesceRequest {}).await?.into_inner();
Ok(response.frozen_count)
}
pub async fn thaw(&mut self) -> BoxliteResult<u32> {
let response = self.client.thaw(ThawRequest {}).await?.into_inner();
Ok(response.thawed_count)
}
}
#[derive(Debug)]
pub struct GuestInitConfig {
pub volumes: Vec<VolumeConfig>,
pub network: Option<NetworkInitConfig>,
}
#[derive(Debug, Clone)]
pub enum VolumeConfig {
Virtiofs {
tag: String,
mount_point: String,
read_only: bool,
container_id: Option<String>,
},
BlockDevice {
device: String,
mount_point: String,
filesystem: Filesystem,
need_format: bool,
need_resize: bool,
},
}
impl VolumeConfig {
pub fn virtiofs(
tag: impl Into<String>,
mount_point: impl Into<String>,
read_only: bool,
container_id: Option<String>,
) -> Self {
Self::Virtiofs {
tag: tag.into(),
mount_point: mount_point.into(),
read_only,
container_id,
}
}
pub fn block_device(
device: impl Into<String>,
mount_point: impl Into<String>,
filesystem: Filesystem,
need_format: bool,
need_resize: bool,
) -> Self {
Self::BlockDevice {
device: device.into(),
mount_point: mount_point.into(),
filesystem,
need_format,
need_resize,
}
}
fn into_proto(self) -> Volume {
match self {
VolumeConfig::Virtiofs {
tag,
mount_point,
read_only,
container_id,
} => Volume {
mount_point,
source: Some(boxlite_shared::volume::Source::Virtiofs(VirtiofsSource {
tag,
read_only,
})),
container_id: container_id.unwrap_or_default(),
},
VolumeConfig::BlockDevice {
device,
mount_point,
filesystem,
need_format,
need_resize,
} => Volume {
mount_point,
source: Some(boxlite_shared::volume::Source::BlockDevice(
BlockDeviceSource {
device,
filesystem: filesystem.into(),
need_format,
need_resize,
},
)),
container_id: String::new(),
},
}
}
}
#[derive(Debug)]
pub struct NetworkInitConfig {
pub interface: String,
pub ip: Option<String>,
pub gateway: Option<String>,
}