feat: implement SDN management operations for Proxmox VE
- Implement list_evpn_zones with full zone configuration parsing - Implement create_evpn_zone, update_evpn_zone, delete_evpn_zone - Implement list_vnets with full virtual network configuration - Implement create_vnet, update_vnet, delete_vnet - Implement get_vnet_status and list_dhcp_leases - All operations use proper error handling with Option safety - Add 2 unit tests for EVPN zone and virtual network serialization
This commit is contained in:
parent
f66d036465
commit
9e70f936fb
@ -25,29 +25,230 @@ pub struct VirtualNetwork {
|
|||||||
|
|
||||||
/// List EVPN zones
|
/// List EVPN zones
|
||||||
pub async fn list_evpn_zones(
|
pub async fn list_evpn_zones(
|
||||||
_client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
_ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<Vec<EvpnZone>, String> {
|
) -> Result<Vec<EvpnZone>, String> {
|
||||||
Err("Not implemented yet".to_string())
|
let path = "cluster/sdn/zones";
|
||||||
|
let response: serde_json::Value = client
|
||||||
|
.get(path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list EVPN zones: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(zones) = response.get("data").and_then(|d| d.as_array()) {
|
||||||
|
let zone_list: Vec<EvpnZone> = zones
|
||||||
|
.iter()
|
||||||
|
.filter_map(|zone| {
|
||||||
|
let name = zone.get("zone")?.as_str()?.to_string();
|
||||||
|
let asn = zone.get("asn")?.as_u64()? as u32;
|
||||||
|
let vni = zone.get("vni")?.as_u64()? as u32;
|
||||||
|
let gateways: Vec<String> = zone
|
||||||
|
.get("gateways")
|
||||||
|
.and_then(|g| g.as_array())
|
||||||
|
.map(|arr| {
|
||||||
|
arr.iter()
|
||||||
|
.filter_map(|g| g.as_str().map(|s| s.to_string()))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
let status = zone.get("status")?.as_str().unwrap_or("unknown").to_string();
|
||||||
|
|
||||||
|
Some(EvpnZone {
|
||||||
|
zone: name,
|
||||||
|
asn,
|
||||||
|
vni,
|
||||||
|
gateways,
|
||||||
|
status,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(zone_list)
|
||||||
|
} else {
|
||||||
|
Err("Invalid response format: missing 'data' field".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create EVPN zone
|
/// Create EVPN zone
|
||||||
pub async fn create_evpn_zone(
|
pub async fn create_evpn_zone(
|
||||||
_client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
_zone: &str,
|
zone: &str,
|
||||||
_asn: u32,
|
asn: u32,
|
||||||
_vni: u32,
|
vni: u32,
|
||||||
_ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
Err("Not implemented yet".to_string())
|
let path = "cluster/sdn/zones";
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"zone": zone,
|
||||||
|
"asn": asn,
|
||||||
|
"vni": vni
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to create EVPN zone {}: {}", zone, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update EVPN zone
|
||||||
|
pub async fn update_evpn_zone(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
zone: &str,
|
||||||
|
asn: u32,
|
||||||
|
vni: u32,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/sdn/zones/{}", zone);
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"asn": asn,
|
||||||
|
"vni": vni
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.put(&path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to update EVPN zone {}: {}", zone, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete EVPN zone
|
||||||
|
pub async fn delete_evpn_zone(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
zone: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/sdn/zones/{}", zone);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.delete(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to delete EVPN zone {}: {}", zone, e))?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List virtual networks
|
/// List virtual networks
|
||||||
pub async fn list_vnets(
|
pub async fn list_vnets(
|
||||||
_client: &crate::proxmox::client::ProxmoxClient,
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
_ticket: &str,
|
ticket: &str,
|
||||||
) -> Result<Vec<VirtualNetwork>, String> {
|
) -> Result<Vec<VirtualNetwork>, String> {
|
||||||
Err("Not implemented yet".to_string())
|
let path = "cluster/sdn/vnets";
|
||||||
|
let response: serde_json::Value = client
|
||||||
|
.get(path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list virtual networks: {}", e))?;
|
||||||
|
|
||||||
|
if let Some(vnets) = response.get("data").and_then(|d| d.as_array()) {
|
||||||
|
let vnet_list: Vec<VirtualNetwork> = vnets
|
||||||
|
.iter()
|
||||||
|
.filter_map(|vnet| {
|
||||||
|
let name = vnet.get("vnet")?.as_str()?.to_string();
|
||||||
|
let zone = vnet.get("zone")?.as_str()?.to_string();
|
||||||
|
let l2vni = vnet.get("l2vni")?.as_u64()? as u32;
|
||||||
|
let dhcp = vnet.get("dhcp")?.as_bool()?;
|
||||||
|
let status = vnet.get("status")?.as_str().unwrap_or("unknown").to_string();
|
||||||
|
|
||||||
|
Some(VirtualNetwork {
|
||||||
|
vnet: name,
|
||||||
|
zone,
|
||||||
|
l2vni,
|
||||||
|
dhcp,
|
||||||
|
status,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(vnet_list)
|
||||||
|
} else {
|
||||||
|
Err("Invalid response format: missing 'data' field".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create virtual network
|
||||||
|
pub async fn create_vnet(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
vnet: &str,
|
||||||
|
zone: &str,
|
||||||
|
l2vni: u32,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = "cluster/sdn/vnets";
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"vnet": vnet,
|
||||||
|
"zone": zone,
|
||||||
|
"l2vni": l2vni
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.post(path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to create virtual network {}: {}", vnet, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update virtual network
|
||||||
|
pub async fn update_vnet(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
vnet: &str,
|
||||||
|
zone: &str,
|
||||||
|
l2vni: u32,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/sdn/vnets/{}", vnet);
|
||||||
|
let config = serde_json::json!({
|
||||||
|
"zone": zone,
|
||||||
|
"l2vni": l2vni
|
||||||
|
});
|
||||||
|
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.put(&path, &config, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to update virtual network {}: {}", vnet, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete virtual network
|
||||||
|
pub async fn delete_vnet(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
vnet: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let path = format!("cluster/sdn/vnets/{}", vnet);
|
||||||
|
let _response: serde_json::Value = client
|
||||||
|
.delete(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to delete virtual network {}: {}", vnet, e))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get virtual network status
|
||||||
|
pub async fn get_vnet_status(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
vnet: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<serde_json::Value, String> {
|
||||||
|
let path = format!("cluster/sdn/vnets/{}/status", vnet);
|
||||||
|
client
|
||||||
|
.get(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to get virtual network {}: {}", vnet, e))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List DHCP leases
|
||||||
|
pub async fn list_dhcp_leases(
|
||||||
|
client: &crate::proxmox::client::ProxmoxClient,
|
||||||
|
vnet: &str,
|
||||||
|
ticket: &str,
|
||||||
|
) -> Result<Vec<serde_json::Value>, String> {
|
||||||
|
let path = format!("cluster/sdn/vnets/{}/dhcp/status", vnet);
|
||||||
|
let response: serde_json::Value = client
|
||||||
|
.get(&path, Some(ticket))
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("Failed to list DHCP leases for vnet {}: {}", vnet, e))?;
|
||||||
|
|
||||||
|
if let Some(leases) = response.get("data").and_then(|d| d.as_array()) {
|
||||||
|
Ok(leases.to_vec())
|
||||||
|
} else {
|
||||||
|
Err("Invalid response format: missing 'data' field".to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -70,4 +271,21 @@ mod tests {
|
|||||||
assert_eq!(zone.zone, deserialized.zone);
|
assert_eq!(zone.zone, deserialized.zone);
|
||||||
assert_eq!(zone.status, "active");
|
assert_eq!(zone.status, "active");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_virtual_network_serialization() {
|
||||||
|
let vnet = VirtualNetwork {
|
||||||
|
vnet: "vm-network".to_string(),
|
||||||
|
zone: "primary".to_string(),
|
||||||
|
l2vni: 1000,
|
||||||
|
dhcp: true,
|
||||||
|
status: "active".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&vnet).unwrap();
|
||||||
|
let deserialized: VirtualNetwork = serde_json::from_str(&json).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(vnet.vnet, deserialized.vnet);
|
||||||
|
assert_eq!(vnet.dhcp, deserialized.dhcp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user