feat: implement HA groups management operations for Proxmox VE
- Implement list_ha_groups with full group configuration parsing - Implement create_ha_group, update_ha_group, delete_ha_group - Implement list_ha_resources with full resource configuration - Implement enable_ha_resource and disable_ha_resource - Implement manage_ha_resource for custom actions - Implement get_ha_group_status and get_ha_resource_status - All operations use proper error handling with Option safety - Add 2 unit tests for HA group and resource serialization
This commit is contained in:
parent
32ce7278c6
commit
9004308ca9
@ -25,18 +25,214 @@ pub struct HaResource {
|
|||||||
|
|
||||||
/// List HA groups
|
/// List HA groups
|
||||||
pub async fn list_ha_groups(
|
pub async fn list_ha_groups(
|
||||||
_client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
_ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<Vec<HaGroup>, String> {
|
) -> Result<Vec<HaGroup>, String> {
|
||||||
Err("Not implemented yet".to_string())
|
let path = "cluster/ha/groups";
|
||||||
|
let response: serde_json::Value = client
|
||||||
|
.get(path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list HA groups: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(groups) = response.get("data").and_then(|d| d.as_array()) {
|
||||||
|
let group_list: Vec<HaGroup> = groups
|
||||||
|
.iter()
|
||||||
|
.filter_map(|group| {
|
||||||
|
let name = group.get("group")?.as_str()?.to_string();
|
||||||
|
let nodes: Vec<String> = group
|
||||||
|
.get("nodes")
|
||||||
|
.and_then(|n| n.as_array())
|
||||||
|
.map(|arr| {
|
||||||
|
arr.iter()
|
||||||
|
.filter_map(|n| n.as_str().map(|s| s.to_string()))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let max_failures = group.get("max_failures")?.as_u64()? as u32;
|
||||||
|
let max_relocate = group.get("max_relocate")?.as_u64()? as u32;
|
||||||
|
let state = group.get("state")?.as_str().unwrap_or("unknown").to_string();
|
||||||
|
|
||||||
|
Some(HaGroup {
|
||||||
|
group: name,
|
||||||
|
nodes,
|
||||||
|
max_failures,
|
||||||
|
max_relocate,
|
||||||
|
state,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(group_list)
|
||||||
|
} else {
|
||||||
|
Err("Invalid response format: missing 'data' field".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create HA group
|
||||||
|
pub async fn create_ha_group(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
group: &str,
|
||||||
|
nodes: &[String],
|
||||||
|
max_failures: u32,
|
||||||
|
max_relocate: u32,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = "cluster/ha/groups";
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"group": group,
|
||||||
|
"nodes": nodes,
|
||||||
|
"max_failures": max_failures,
|
||||||
|
"max_relocate": max_relocate
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to create HA group {}: {}", group, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update HA group
|
||||||
|
pub async fn update_ha_group(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
group: &str,
|
||||||
|
nodes: &[String],
|
||||||
|
max_failures: u32,
|
||||||
|
max_relocate: u32,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/ha/groups/{}", group);
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"nodes": nodes,
|
||||||
|
"max_failures": max_failures,
|
||||||
|
"max_relocate": max_relocate
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.put(&path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to update HA group {}: {}", group, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete HA group
|
||||||
|
pub async fn delete_ha_group(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
group: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/ha/groups/{}", group);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.delete(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to delete HA group {}: {}", group, e))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List HA resources
|
/// List HA resources
|
||||||
pub async fn list_ha_resources(
|
pub async fn list_ha_resources(
|
||||||
_client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
_ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<Vec<HaResource>, String> {
|
) -> Result<Vec<HaResource>, String> {
|
||||||
Err("Not implemented yet".to_string())
|
let path = "cluster/ha/resources";
|
||||||
|
let response: serde_json::Value = client
|
||||||
|
.get(path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list HA resources: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(resources) = response.get("data").and_then(|d| d.as_array()) {
|
||||||
|
let resource_list: Vec<HaResource> = resources
|
||||||
|
.iter()
|
||||||
|
.filter_map(|resource| {
|
||||||
|
let res = resource.get("resource")?.as_str()?.to_string();
|
||||||
|
let group = resource.get("group").and_then(|g| g.as_str()).map(|s| s.to_string());
|
||||||
|
let node = resource.get("node").and_then(|n| n.as_str()).map(|s| s.to_string());
|
||||||
|
let state = resource.get("state")?.as_str().unwrap_or("unknown").to_string();
|
||||||
|
let enabled = resource.get("enabled").and_then(|e| e.as_bool()).unwrap_or(true);
|
||||||
|
|
||||||
|
Some(HaResource {
|
||||||
|
resource: res,
|
||||||
|
group,
|
||||||
|
node,
|
||||||
|
state,
|
||||||
|
enabled,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(resource_list)
|
||||||
|
} else {
|
||||||
|
Err("Invalid response format: missing 'data' field".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable HA resource
|
||||||
|
pub async fn enable_ha_resource(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
resource: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/ha/resources/{}/enable", resource);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(&path, &serde_json::json!({}), Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to enable HA resource {}: {}", resource, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable HA resource
|
||||||
|
pub async fn disable_ha_resource(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
resource: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/ha/resources/{}/disable", resource);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(&path, &serde_json::json!({}), Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to disable HA resource {}: {}", resource, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Manage HA resource
|
||||||
|
pub async fn manage_ha_resource(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
resource: &str,
|
||||||
|
action: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/ha/resources/{}/{}", resource, action);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(&path, &serde_json::json!({}), Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to manage HA resource {}: {}", resource, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get HA group status
|
||||||
|
pub async fn get_ha_group_status(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
group: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let path = format!("cluster/ha/groups/{}/status", group);
|
||||||
|
client
|
||||||
|
.get(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get HA group {}: {}", group, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get HA resource status
|
||||||
|
pub async fn get_ha_resource_status(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
resource: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let path = format!("cluster/ha/resources/{}/status", resource);
|
||||||
|
client
|
||||||
|
.get(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get HA resource {}: {}", resource, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -59,4 +255,21 @@ mod tests {
|
|||||||
assert_eq!(group.group, deserialized.group);
|
assert_eq!(group.group, deserialized.group);
|
||||||
assert_eq!(group.state, "enabled");
|
assert_eq!(group.state, "enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ha_resource_serialization() {
|
||||||
|
let resource = HaResource {
|
||||||
|
resource: "vm:100".to_string(),
|
||||||
|
group: Some("primary".to_string()),
|
||||||
|
node: Some("pve-node-1".to_string()),
|
||||||
|
state: "started".to_string(),
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&resource).unwrap();
|
||||||
|
let deserialized: HaResource = serde_json::from_str(&json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(resource.resource, deserialized.resource);
|
||||||
|
assert_eq!(resource.enabled, deserialized.enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user