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)]
#[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineDomainReq(pub DomainXML);
pub struct DefineDomainReq(pub VMInfo, pub DomainXML);
impl Handler<DefineDomainReq> for LibVirtActor {
type Result = anyhow::Result<XMLUuid>;
fn handle(&mut self, msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
let xml = msg.0.into_xml()?;
fn handle(&mut self, mut msg: DefineDomainReq, _ctx: &mut Self::Context) -> Self::Result {
let xml = msg.1.into_xml()?;
log::debug!("Define domain:\n{}", 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)?;
}
// 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
domain.undefine_flags(match msg.keep_files {
true => sys::VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
@ -360,20 +373,27 @@ impl Handler<SetDomainAutostart> for LibVirtActor {
#[derive(Message)]
#[rtype(result = "anyhow::Result<XMLUuid>")]
pub struct DefineNetwork(pub NetworkXML);
pub struct DefineNetwork(pub NetworkInfo, pub NetworkXML);
impl Handler<DefineNetwork> for LibVirtActor {
type Result = anyhow::Result<XMLUuid>;
fn handle(&mut self, msg: DefineNetwork, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Define network: {:?}", msg.0);
fn handle(&mut self, mut msg: DefineNetwork, _ctx: &mut Self::Context) -> Self::Result {
log::debug!("Define network: {:?}", msg.1);
log::debug!("Source network structure: {:#?}", msg.0);
let network_xml = msg.0.into_xml()?;
log::debug!("Source network structure: {:#?}", msg.1);
let network_xml = msg.1.into_xml()?;
log::debug!("Define network XML: {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 {
log::debug!("Delete network: {}\n", 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()?;
// Remove backup definition
let backup_definition = AppConfig::get().net_definition_path(&network_name);
if backup_definition.exists() {
std::fs::remove_file(backup_definition)?;
}
Ok(())
}
}

View File

@ -240,6 +240,18 @@ impl AppConfig {
pub fn vm_storage_path(&self, id: XMLUuid) -> PathBuf {
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)]

View File

@ -10,7 +10,7 @@ pub struct NetworkID {
/// Create a new network
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,
Err(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,
Err(e) => {
log::error!("Failed to update network! {e}");
@ -71,7 +71,7 @@ pub async fn update(
path: web::Path<NetworkID>,
body: web::Json<NetworkInfo>,
) -> HttpResult {
let mut network = match body.0.to_virt_network() {
let mut network = match body.0.as_virt_network() {
Ok(n) => n,
Err(e) => {
log::error!("Failed to extract network info! {e}");
@ -82,7 +82,7 @@ pub async fn update(
};
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}");
return Ok(
HttpResponse::InternalServerError().json(format!("Failed to update network!\n${e}"))

View File

@ -20,7 +20,7 @@ struct VMUuid {
/// Create a new VM
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,
Err(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,
Err(e) => {
log::error!("Failed to update domain info! {e}");
@ -111,13 +111,13 @@ pub async fn update(
id: web::Path<SingleVMUUidReq>,
req: web::Json<VMInfo>,
) -> 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}");
HttpResponse::BadRequest().json(format!("Failed to extract domain info! {e}"))
})?;
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}");
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::LibVirtActor;
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;
#[derive(Clone)]
@ -36,8 +36,10 @@ impl LibVirtClient {
}
/// Update a domain
pub async fn update_domain(&self, xml: DomainXML) -> anyhow::Result<XMLUuid> {
self.0.send(libvirt_actor::DefineDomainReq(xml)).await?
pub async fn update_domain(&self, vm_def: VMInfo, xml: DomainXML) -> anyhow::Result<XMLUuid> {
self.0
.send(libvirt_actor::DefineDomainReq(vm_def, xml))
.await?
}
/// Delete a domain
@ -100,8 +102,14 @@ impl LibVirtClient {
}
/// Update a network configuration
pub async fn update_network(&self, network: NetworkXML) -> anyhow::Result<XMLUuid> {
self.0.send(libvirt_actor::DefineNetwork(network)).await?
pub async fn update_network(
&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

View File

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