Automatically backup source network and VM configuration
This commit is contained in:
parent
d053490a47
commit
d8a6b58c52
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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)]
|
||||
|
@ -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}"))
|
||||
|
@ -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}")));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user