use crate::api::machine::{
reset_request::WipeMode as ProtoWipeMode, Reset as ProtoReset,
ResetPartitionSpec as ProtoPartitionSpec, ResetRequest as ProtoRequest,
ResetResponse as ProtoResponse,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WipeMode {
#[default]
All,
SystemDisk,
UserDisks,
}
impl From<WipeMode> for i32 {
fn from(mode: WipeMode) -> Self {
match mode {
WipeMode::All => ProtoWipeMode::All as i32,
WipeMode::SystemDisk => ProtoWipeMode::SystemDisk as i32,
WipeMode::UserDisks => ProtoWipeMode::UserDisks as i32,
}
}
}
impl From<i32> for WipeMode {
fn from(value: i32) -> Self {
match value {
0 => WipeMode::All,
1 => WipeMode::SystemDisk,
2 => WipeMode::UserDisks,
_ => WipeMode::All,
}
}
}
impl std::fmt::Display for WipeMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WipeMode::All => write!(f, "all"),
WipeMode::SystemDisk => write!(f, "system-disk"),
WipeMode::UserDisks => write!(f, "user-disks"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResetPartitionSpec {
pub label: String,
pub wipe: bool,
}
impl ResetPartitionSpec {
#[must_use]
pub fn new(label: impl Into<String>, wipe: bool) -> Self {
Self {
label: label.into(),
wipe,
}
}
#[must_use]
pub fn wipe(label: impl Into<String>) -> Self {
Self::new(label, true)
}
}
impl From<ResetPartitionSpec> for ProtoPartitionSpec {
fn from(spec: ResetPartitionSpec) -> Self {
ProtoPartitionSpec {
label: spec.label,
wipe: spec.wipe,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ResetRequest {
pub graceful: bool,
pub reboot: bool,
pub system_partitions_to_wipe: Vec<ResetPartitionSpec>,
pub user_disks_to_wipe: Vec<String>,
pub mode: WipeMode,
}
impl ResetRequest {
#[must_use]
pub fn builder() -> ResetRequestBuilder {
ResetRequestBuilder::default()
}
#[must_use]
pub fn graceful() -> Self {
Self {
graceful: true,
reboot: true,
mode: WipeMode::All,
..Default::default()
}
}
#[must_use]
pub fn force() -> Self {
Self {
graceful: false,
reboot: true,
mode: WipeMode::All,
..Default::default()
}
}
#[must_use]
pub fn halt() -> Self {
Self {
graceful: true,
reboot: false,
mode: WipeMode::All,
..Default::default()
}
}
}
impl From<ResetRequest> for ProtoRequest {
fn from(req: ResetRequest) -> Self {
ProtoRequest {
graceful: req.graceful,
reboot: req.reboot,
system_partitions_to_wipe: req
.system_partitions_to_wipe
.into_iter()
.map(Into::into)
.collect(),
user_disks_to_wipe: req.user_disks_to_wipe,
mode: req.mode.into(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ResetRequestBuilder {
graceful: bool,
reboot: bool,
system_partitions_to_wipe: Vec<ResetPartitionSpec>,
user_disks_to_wipe: Vec<String>,
mode: WipeMode,
}
impl ResetRequestBuilder {
#[must_use]
pub fn graceful(mut self, graceful: bool) -> Self {
self.graceful = graceful;
self
}
#[must_use]
pub fn reboot(mut self, reboot: bool) -> Self {
self.reboot = reboot;
self
}
#[must_use]
pub fn wipe_mode(mut self, mode: WipeMode) -> Self {
self.mode = mode;
self
}
#[must_use]
pub fn wipe_partition(mut self, spec: ResetPartitionSpec) -> Self {
self.system_partitions_to_wipe.push(spec);
self
}
#[must_use]
pub fn wipe_user_disk(mut self, disk: impl Into<String>) -> Self {
self.user_disks_to_wipe.push(disk.into());
self
}
#[must_use]
pub fn build(self) -> ResetRequest {
ResetRequest {
graceful: self.graceful,
reboot: self.reboot,
system_partitions_to_wipe: self.system_partitions_to_wipe,
user_disks_to_wipe: self.user_disks_to_wipe,
mode: self.mode,
}
}
}
#[derive(Debug, Clone)]
pub struct ResetResult {
pub node: Option<String>,
pub actor_id: String,
}
impl From<ProtoReset> for ResetResult {
fn from(proto: ProtoReset) -> Self {
Self {
node: proto.metadata.map(|m| m.hostname),
actor_id: proto.actor_id,
}
}
}
#[derive(Debug, Clone)]
pub struct ResetResponse {
pub results: Vec<ResetResult>,
}
impl From<ProtoResponse> for ResetResponse {
fn from(proto: ProtoResponse) -> Self {
Self {
results: proto.messages.into_iter().map(Into::into).collect(),
}
}
}
impl ResetResponse {
#[must_use]
pub fn is_success(&self) -> bool {
!self.results.is_empty()
}
#[must_use]
pub fn first(&self) -> Option<&ResetResult> {
self.results.first()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wipe_mode_conversion() {
assert_eq!(i32::from(WipeMode::All), 0);
assert_eq!(i32::from(WipeMode::SystemDisk), 1);
assert_eq!(i32::from(WipeMode::UserDisks), 2);
assert_eq!(WipeMode::from(0), WipeMode::All);
assert_eq!(WipeMode::from(1), WipeMode::SystemDisk);
assert_eq!(WipeMode::from(2), WipeMode::UserDisks);
}
#[test]
fn test_wipe_mode_display() {
assert_eq!(WipeMode::All.to_string(), "all");
assert_eq!(WipeMode::SystemDisk.to_string(), "system-disk");
assert_eq!(WipeMode::UserDisks.to_string(), "user-disks");
}
#[test]
fn test_reset_request_graceful() {
let request = ResetRequest::graceful();
assert!(request.graceful);
assert!(request.reboot);
assert_eq!(request.mode, WipeMode::All);
}
#[test]
fn test_reset_request_force() {
let request = ResetRequest::force();
assert!(!request.graceful);
assert!(request.reboot);
assert_eq!(request.mode, WipeMode::All);
}
#[test]
fn test_reset_request_halt() {
let request = ResetRequest::halt();
assert!(request.graceful);
assert!(!request.reboot);
}
#[test]
fn test_reset_request_builder() {
let request = ResetRequest::builder()
.graceful(true)
.reboot(false)
.wipe_mode(WipeMode::SystemDisk)
.wipe_partition(ResetPartitionSpec::wipe("STATE"))
.wipe_user_disk("/dev/sdb")
.build();
assert!(request.graceful);
assert!(!request.reboot);
assert_eq!(request.mode, WipeMode::SystemDisk);
assert_eq!(request.system_partitions_to_wipe.len(), 1);
assert_eq!(request.user_disks_to_wipe.len(), 1);
}
#[test]
fn test_proto_conversion() {
let request = ResetRequest::builder()
.graceful(true)
.reboot(true)
.wipe_mode(WipeMode::UserDisks)
.build();
let proto: ProtoRequest = request.into();
assert!(proto.graceful);
assert!(proto.reboot);
assert_eq!(proto.mode, ProtoWipeMode::UserDisks as i32);
}
#[test]
fn test_partition_spec() {
let spec = ResetPartitionSpec::wipe("STATE");
assert_eq!(spec.label, "STATE");
assert!(spec.wipe);
let spec2 = ResetPartitionSpec::new("EPHEMERAL", false);
assert_eq!(spec2.label, "EPHEMERAL");
assert!(!spec2.wipe);
}
#[test]
fn test_reset_response_is_success() {
let response = ResetResponse {
results: vec![ResetResult {
node: Some("node1".to_string()),
actor_id: "actor-123".to_string(),
}],
};
assert!(response.is_success());
let empty = ResetResponse { results: vec![] };
assert!(!empty.is_success());
}
}