Automatically backup source network and VM configuration

This commit is contained in:
Pierre HUBERT 2023-12-23 18:12:46 +01:00
parent d053490a47
commit d8a6b58c52
7 changed files with 106 additions and 51 deletions

View File

@ -115,17 +115,24 @@ impl Handler<GetSourceDomainXMLReq> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<XMLUuid>")] #[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineDomainReq(pub DomainXML); pub struct DefineDomainReq(pub VMInfo, pub DomainXML);
impl Handler<DefineDomainReq> for LibVirtActor { impl Handler<DefineDomainReq> for LibVirtActor {
type Result = anyhow::Result<XMLUuid>; type Result = anyhow::Result<XMLUuid>;
fn handle(&mut self, msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, mut msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
let xml = msg.0.into_xml()?; let xml = msg.1.into_xml()?;
log::debug!("Define domain:\n{}", xml); log::debug!("Define domain:\n{}", xml);
let domain = Domain::define_xml(&self.m, &xml)?; let domain = Domain::define_xml(&self.m, &xml)?;
XMLUuid::parse_from_str(&domain.get_uuid_string()?) let uuid = XMLUuid::parse_from_str(&domain.get_uuid_string()?)?;
// Save a copy of the source definition
msg.0.uuid = Some(uuid);
let json = serde_json::to_string(&msg.0)?;
std::fs::write(AppConfig::get().vm_definition_path(&msg.0.name), json)?;
Ok(uuid)
} }
} }
@ -155,6 +162,12 @@ impl Handler<DeleteDomainReq> for LibVirtActor {
std::fs::remove_file(vnc_socket)?; std::fs::remove_file(vnc_socket)?;
} }
// Remove backup definition
let backup_definition = AppConfig::get().vm_definition_path(&domain_name);
if backup_definition.exists() {
std::fs::remove_file(backup_definition)?;
}
// Delete the domain // Delete the domain
domain.undefine_flags(match msg.keep_files { domain.undefine_flags(match msg.keep_files {
true => sys::VIR_DOMAIN_UNDEFINE_KEEP_NVRAM, true => sys::VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
@ -360,20 +373,27 @@ impl Handler<SetDomainAutostart> for LibVirtActor {
#[derive(Message)] #[derive(Message)]
#[rtype(result = "anyhow::Result<XMLUuid>")] #[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineNetwork(pub NetworkXML); pub struct DefineNetwork(pub NetworkInfo, pub NetworkXML);
impl Handler<DefineNetwork> for LibVirtActor { impl Handler<DefineNetwork> for LibVirtActor {
type Result = anyhow::Result<XMLUuid>; type Result = anyhow::Result<XMLUuid>;
fn handle(&mut self, msg: DefineNetwork, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, mut msg: DefineNetwork, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Define network: {:?}", msg.0); log::debug!("Define network: {:?}", msg.1);
log::debug!("Source network structure: {:#?}", msg.0); log::debug!("Source network structure: {:#?}", msg.1);
let network_xml = msg.0.into_xml()?; let network_xml = msg.1.into_xml()?;
log::debug!("Define network XML: {network_xml}"); log::debug!("Define network XML: {network_xml}");
let network = Network::define_xml(&self.m, &network_xml)?; let network = Network::define_xml(&self.m, &network_xml)?;
XMLUuid::parse_from_str(&network.get_uuid_string()?) let uuid = XMLUuid::parse_from_str(&network.get_uuid_string()?)?;
// Save a copy of the source definition
msg.0.uuid = Some(uuid);
let json = serde_json::to_string(&msg.0)?;
std::fs::write(AppConfig::get().net_definition_path(&msg.0.name), json)?;
Ok(uuid)
} }
} }
@ -437,7 +457,15 @@ impl Handler<DeleteNetwork> for LibVirtActor {
fn handle(&mut self, msg: DeleteNetwork, _ctx: &mut Self::Context) -> Self::Result { fn handle(&mut self, msg: DeleteNetwork, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Delete network: {}\n", msg.0.as_string()); log::debug!("Delete network: {}\n", msg.0.as_string());
let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?;
let network_name = network.get_name()?;
network.undefine()?; network.undefine()?;
// Remove backup definition
let backup_definition = AppConfig::get().net_definition_path(&network_name);
if backup_definition.exists() {
std::fs::remove_file(backup_definition)?;
}
Ok(()) Ok(())
} }
} }

View File

@ -240,6 +240,18 @@ impl AppConfig {
pub fn vm_storage_path(&self, id: XMLUuid) -> PathBuf { pub fn vm_storage_path(&self, id: XMLUuid) -> PathBuf {
self.disks_storage_path().join(id.as_string()) self.disks_storage_path().join(id.as_string())
} }
pub fn definitions_path(&self) -> PathBuf {
self.storage_path().join("definitions")
}
pub fn vm_definition_path(&self, name: &str) -> PathBuf {
self.definitions_path().join(format!("vm-{name}.json"))
}
pub fn net_definition_path(&self, name: &str) -> PathBuf {
self.definitions_path().join(format!("net-{name}.json"))
}
} }
#[derive(Debug, Clone, serde::Serialize)] #[derive(Debug, Clone, serde::Serialize)]

View File

@ -10,7 +10,7 @@ pub struct NetworkID {
/// Create a new network /// Create a new network
pub async fn create(client: LibVirtReq, req: web::Json<NetworkInfo>) -> HttpResult { pub async fn create(client: LibVirtReq, req: web::Json<NetworkInfo>) -> HttpResult {
let network = match req.0.to_virt_network() { let network = match req.0.as_virt_network() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract network info! {e}"); log::error!("Failed to extract network info! {e}");
@ -20,7 +20,7 @@ pub async fn create(client: LibVirtReq, req: web::Json<NetworkInfo>) -> HttpResu
} }
}; };
let uid = match client.update_network(network).await { let uid = match client.update_network(req.0, network).await {
Ok(u) => u, Ok(u) => u,
Err(e) => { Err(e) => {
log::error!("Failed to update network! {e}"); log::error!("Failed to update network! {e}");
@ -71,7 +71,7 @@ pub async fn update(
path: web::Path<NetworkID>, path: web::Path<NetworkID>,
body: web::Json<NetworkInfo>, body: web::Json<NetworkInfo>,
) -> HttpResult { ) -> HttpResult {
let mut network = match body.0.to_virt_network() { let mut network = match body.0.as_virt_network() {
Ok(n) => n, Ok(n) => n,
Err(e) => { Err(e) => {
log::error!("Failed to extract network info! {e}"); log::error!("Failed to extract network info! {e}");
@ -82,7 +82,7 @@ pub async fn update(
}; };
network.uuid = Some(path.uid); network.uuid = Some(path.uid);
if let Err(e) = client.update_network(network).await { if let Err(e) = client.update_network(body.0, network).await {
log::error!("Failed to update network! {e}"); log::error!("Failed to update network! {e}");
return Ok( return Ok(
HttpResponse::InternalServerError().json(format!("Failed to update network!\n${e}")) HttpResponse::InternalServerError().json(format!("Failed to update network!\n${e}"))

View File

@ -20,7 +20,7 @@ struct VMUuid {
/// Create a new VM /// Create a new VM
pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult { pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
let domain = match req.0.to_domain() { let domain = match req.0.as_tomain() {
Ok(d) => d, Ok(d) => d,
Err(e) => { Err(e) => {
log::error!("Failed to extract domain info! {e}"); log::error!("Failed to extract domain info! {e}");
@ -29,7 +29,7 @@ pub async fn create(client: LibVirtReq, req: web::Json<VMInfo>) -> HttpResult {
); );
} }
}; };
let id = match client.update_domain(domain).await { let id = match client.update_domain(req.0, domain).await {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
log::error!("Failed to update domain info! {e}"); log::error!("Failed to update domain info! {e}");
@ -111,13 +111,13 @@ pub async fn update(
id: web::Path<SingleVMUUidReq>, id: web::Path<SingleVMUUidReq>,
req: web::Json<VMInfo>, req: web::Json<VMInfo>,
) -> HttpResult { ) -> HttpResult {
let mut domain = req.0.to_domain().map_err(|e| { let mut domain = req.0.as_tomain().map_err(|e| {
log::error!("Failed to extract domain info! {e}"); log::error!("Failed to extract domain info! {e}");
HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}")) HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
})?; })?;
domain.uuid = Some(id.uid); domain.uuid = Some(id.uid);
if let Err(e) = client.update_domain(domain).await { if let Err(e) = client.update_domain(req.0, domain).await {
log::error!("Failed to update domain info! {e}"); log::error!("Failed to update domain info! {e}");
return Ok(HttpResponse::BadRequest().json(format!("Failed to update domain info!\n{e}"))); return Ok(HttpResponse::BadRequest().json(format!("Failed to update domain info!\n{e}")));
} }

View File

@ -1,7 +1,7 @@
use crate::actors::libvirt_actor; use crate::actors::libvirt_actor;
use crate::actors::libvirt_actor::LibVirtActor; use crate::actors::libvirt_actor::LibVirtActor;
use crate::libvirt_lib_structures::{DomainState, DomainXML, NetworkXML, XMLUuid}; use crate::libvirt_lib_structures::{DomainState, DomainXML, NetworkXML, XMLUuid};
use crate::libvirt_rest_structures::HypervisorInfo; use crate::libvirt_rest_structures::{HypervisorInfo, NetworkInfo, VMInfo};
use actix::Addr; use actix::Addr;
#[derive(Clone)] #[derive(Clone)]
@ -36,8 +36,10 @@ impl LibVirtClient {
} }
/// Update a domain /// Update a domain
pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<XMLUuid> { pub async fn update_domain(&self, vm_def: VMInfo, xml: DomainXML) -> anyhow::Result<XMLUuid> {
self.0.send(libvirt_actor::DefineDomainReq(xml)).await? self.0
.send(libvirt_actor::DefineDomainReq(vm_def, xml))
.await?
} }
/// Delete a domain /// Delete a domain
@ -100,8 +102,14 @@ impl LibVirtClient {
} }
/// Update a network configuration /// Update a network configuration
pub async fn update_network(&self, network: NetworkXML) -> anyhow::Result<XMLUuid> { pub async fn update_network(
self.0.send(libvirt_actor::DefineNetwork(network)).await? &self,
net_def: NetworkInfo,
network: NetworkXML,
) -> anyhow::Result<XMLUuid> {
self.0
.send(libvirt_actor::DefineNetwork(net_def, network))
.await?
} }
/// Get the full list of networks /// Get the full list of networks

View File

@ -108,7 +108,7 @@ pub struct VMInfo {
impl VMInfo { impl VMInfo {
/// Turn this VM into a domain /// Turn this VM into a domain
pub fn to_domain(self) -> anyhow::Result<DomainXML> { pub fn as_tomain(&self) -> anyhow::Result<DomainXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtraction("VM name is invalid!").into()); return Err(StructureExtraction("VM name is invalid!").into());
} }
@ -207,7 +207,7 @@ impl VMInfo {
} }
// Apply disks configuration // Apply disks configuration
for disk in self.disks { for disk in &self.disks {
disk.check_config()?; disk.check_config()?;
disk.apply_config(uuid)?; disk.apply_config(uuid)?;
@ -242,28 +242,34 @@ impl VMInfo {
} }
let mut networks = vec![]; let mut networks = vec![];
for n in self.networks { for n in &self.networks {
networks.push(match n.r#type { networks.push(match &n.r#type {
NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML { NetworkType::UserspaceSLIRPStack => DomainNetInterfaceXML {
mac: NetMacAddress { address: n.mac }, mac: NetMacAddress {
address: n.mac.to_string(),
},
r#type: "user".to_string(), r#type: "user".to_string(),
source: None, source: None,
}, },
NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML { NetworkType::DefinedNetwork { network } => DomainNetInterfaceXML {
mac: NetMacAddress { address: n.mac }, mac: NetMacAddress {
address: n.mac.to_string(),
},
r#type: "network".to_string(), r#type: "network".to_string(),
source: Some(NetIntSourceXML { network }), source: Some(NetIntSourceXML {
network: network.to_string(),
}),
}, },
}) })
} }
Ok(DomainXML { Ok(DomainXML {
r#type: "kvm".to_string(), r#type: "kvm".to_string(),
name: self.name, name: self.name.to_string(),
uuid: Some(uuid), uuid: Some(uuid),
genid: self.genid.map(|i| i.0), genid: self.genid.map(|i| i.0),
title: self.title, title: self.title.clone(),
description: self.description, description: self.description.clone(),
os: OSXML { os: OSXML {
r#type: OSTypeXML { r#type: OSTypeXML {
@ -487,8 +493,8 @@ pub struct IPV6Config {
/// Network configuration /// Network configuration
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct NetworkInfo { pub struct NetworkInfo {
name: String, pub name: String,
uuid: Option<XMLUuid>, pub uuid: Option<XMLUuid>,
title: Option<String>, title: Option<String>,
description: Option<String>, description: Option<String>,
forward_mode: NetworkForwardMode, forward_mode: NetworkForwardMode,
@ -501,7 +507,7 @@ pub struct NetworkInfo {
} }
impl NetworkInfo { impl NetworkInfo {
pub fn to_virt_network(self) -> anyhow::Result<NetworkXML> { pub fn as_virt_network(&self) -> anyhow::Result<NetworkXML> {
if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) { if !regex!("^[a-zA-Z0-9]+$").is_match(&self.name) {
return Err(StructureExtraction("network name is invalid!").into()); return Err(StructureExtraction("network name is invalid!").into());
} }
@ -532,7 +538,7 @@ impl NetworkInfo {
let mut ips = Vec::with_capacity(2); let mut ips = Vec::with_capacity(2);
if let Some(ipv4) = self.ip_v4 { if let Some(ipv4) = &self.ip_v4 {
if ipv4.prefix > 32 { if ipv4.prefix > 32 {
return Err(StructureExtraction("IPv4 prefix is invalid!").into()); return Err(StructureExtraction("IPv4 prefix is invalid!").into());
} }
@ -545,17 +551,17 @@ impl NetworkInfo {
.unwrap() .unwrap()
.mask() .mask()
.into(), .into(),
dhcp: ipv4.dhcp.map(|dhcp| NetworkDHCPXML { dhcp: ipv4.dhcp.as_ref().map(|dhcp| NetworkDHCPXML {
range: NetworkDHCPRangeXML { range: NetworkDHCPRangeXML {
start: IpAddr::V4(dhcp.start), start: IpAddr::V4(dhcp.start),
end: IpAddr::V4(dhcp.end), end: IpAddr::V4(dhcp.end),
}, },
hosts: dhcp hosts: dhcp
.hosts .hosts
.into_iter() .iter()
.map(|c| NetworkDHCPHostXML { .map(|c| NetworkDHCPHostXML {
mac: c.mac, mac: c.mac.to_string(),
name: c.name, name: c.name.to_string(),
ip: c.ip.into(), ip: c.ip.into(),
}) })
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -563,7 +569,7 @@ impl NetworkInfo {
}) })
} }
if let Some(ipv6) = self.ip_v6 { if let Some(ipv6) = &self.ip_v6 {
ips.push(NetworkIPXML { ips.push(NetworkIPXML {
family: "ipv6".to_string(), family: "ipv6".to_string(),
address: IpAddr::V6(ipv6.bridge_address), address: IpAddr::V6(ipv6.bridge_address),
@ -572,17 +578,17 @@ impl NetworkInfo {
.unwrap() .unwrap()
.mask() .mask()
.into(), .into(),
dhcp: ipv6.dhcp.map(|dhcp| NetworkDHCPXML { dhcp: ipv6.dhcp.as_ref().map(|dhcp| NetworkDHCPXML {
range: NetworkDHCPRangeXML { range: NetworkDHCPRangeXML {
start: IpAddr::V6(dhcp.start), start: IpAddr::V6(dhcp.start),
end: IpAddr::V6(dhcp.end), end: IpAddr::V6(dhcp.end),
}, },
hosts: dhcp hosts: dhcp
.hosts .hosts
.into_iter() .iter()
.map(|h| NetworkDHCPHostXML { .map(|h| NetworkDHCPHostXML {
mac: "".to_string(), mac: "".to_string(),
name: h.name, name: h.name.to_string(),
ip: h.ip.into(), ip: h.ip.into(),
}) })
.collect(), .collect(),
@ -591,24 +597,24 @@ impl NetworkInfo {
} }
Ok(NetworkXML { Ok(NetworkXML {
name: self.name, name: self.name.to_string(),
uuid: self.uuid, uuid: self.uuid,
title: self.title, title: self.title.clone(),
description: self.description, description: self.description.clone(),
forward: match self.forward_mode { forward: match self.forward_mode {
NetworkForwardMode::NAT => Some(NetworkForwardXML { NetworkForwardMode::NAT => Some(NetworkForwardXML {
mode: "nat".to_string(), mode: "nat".to_string(),
dev: self.device.unwrap_or_default(), dev: self.device.clone().unwrap_or_default(),
}), }),
NetworkForwardMode::Isolated => None, NetworkForwardMode::Isolated => None,
}, },
bridge: self.bridge_name.map(|b| NetworkBridgeXML { bridge: self.bridge_name.clone().map(|b| NetworkBridgeXML {
name: b.to_string(), name: b.to_string(),
}), }),
dns: self.dns_server.map(|addr| NetworkDNSXML { dns: self.dns_server.map(|addr| NetworkDNSXML {
forwarder: NetworkDNSForwarderXML { addr }, forwarder: NetworkDNSForwarderXML { addr },
}), }),
domain: self.domain.map(|name| NetworkDomainXML { name }), domain: self.domain.clone().map(|name| NetworkDomainXML { name }),
ips, ips,
}) })
} }

View File

@ -41,6 +41,7 @@ async fn main() -> std::io::Result<()> {
files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap();
files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap(); files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap();
let conn = Data::new(LibVirtClient( let conn = Data::new(LibVirtClient(
LibVirtActor::connect() LibVirtActor::connect()