use crate::app_config::AppConfig; use crate::libvirt_lib_structures::{ DomainState, DomainXML, NetworkFilterXML, NetworkXML, XMLUuid, }; use crate::libvirt_rest_structures::*; use actix::{Actor, Context, Handler, Message}; use image::ImageOutputFormat; use std::io::Cursor; use virt::connect::Connect; use virt::domain::Domain; use virt::network::Network; use virt::nwfilter::NWFilter; use virt::stream::Stream; use virt::sys; use virt::sys::VIR_DOMAIN_XML_SECURE; pub struct LibVirtActor { m: Connect, } impl LibVirtActor { /// Connect to hypervisor pub async fn connect() -> anyhow::Result { let hypervisor_uri = AppConfig::get().hypervisor_uri.as_deref().unwrap_or(""); log::info!( "Will connect to hypvervisor at address '{}'", hypervisor_uri ); let conn = Connect::open(hypervisor_uri)?; Ok(Self { m: conn }) } } impl Actor for LibVirtActor { type Context = Context; } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetHypervisorInfo; impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, _msg: GetHypervisorInfo, _ctx: &mut Self::Context) -> Self::Result { let node = self.m.get_node_info()?; Ok(HypervisorInfo { r#type: self.m.get_type()?, hyp_version: self.m.get_hyp_version()?, lib_version: self.m.get_lib_version()?, capabilities: self.m.get_capabilities()?, free_memory: self.m.get_free_memory()?, hostname: self.m.get_hostname()?, node: HypervisorNodeInfo { cpu_model: node.model, memory_size: node.memory, number_of_active_cpus: node.cpus, cpu_frequency_mhz: node.mhz, number_of_numa_cell: node.nodes, number_of_cpu_socket_per_node: node.sockets, number_of_core_per_sockets: node.cores, number_of_threads_per_core: node.threads, }, }) } } #[derive(Message)] #[rtype(result = "anyhow::Result>")] pub struct GetDomainsListReq; impl Handler for LibVirtActor { type Result = anyhow::Result>; fn handle(&mut self, _msg: GetDomainsListReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get full list of domains"); let domains = self.m.list_all_domains(0)?; let mut ids = Vec::with_capacity(domains.len()); for d in domains { ids.push(XMLUuid::parse_from_str(&d.get_uuid_string()?)?); } Ok(ids) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetDomainXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetDomainXMLReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get domain XML:\n{}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let xml = domain.get_xml_desc(VIR_DOMAIN_XML_SECURE)?; log::debug!("XML = {}", xml); Ok(serde_xml_rs::from_str(&xml)?) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetSourceDomainXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetSourceDomainXMLReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get domain source XML:\n{}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; Ok(domain.get_xml_desc(VIR_DOMAIN_XML_SECURE)?) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct DefineDomainReq(pub VMInfo, pub DomainXML); impl Handler for LibVirtActor { type Result = anyhow::Result; 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)?; 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) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct DeleteDomainReq { pub id: XMLUuid, pub keep_files: bool, } impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: DeleteDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!( "Delete domain: {:?} (keep files: {})", msg.id, msg.keep_files ); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.id.as_string())?; let domain_name = domain.get_name()?; // Remove VNC socket let vnc_socket = AppConfig::get().vnc_socket_for_domain(&domain_name); if vnc_socket.exists() { 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, false => sys::VIR_DOMAIN_UNDEFINE_NVRAM, })?; if !msg.keep_files { log::info!("Delete storage associated with the domain"); let path = AppConfig::get().vm_storage_path(msg.id); if path.exists() { std::fs::remove_dir_all(path)?; } } Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetDomainStateReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetDomainStateReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get domain state: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let (state, _) = domain.get_state()?; Ok(match state { sys::VIR_DOMAIN_NOSTATE => DomainState::NoState, sys::VIR_DOMAIN_RUNNING => DomainState::Running, sys::VIR_DOMAIN_BLOCKED => DomainState::Blocked, sys::VIR_DOMAIN_PAUSED => DomainState::Paused, sys::VIR_DOMAIN_SHUTDOWN => DomainState::Shutdown, sys::VIR_DOMAIN_SHUTOFF => DomainState::Shutoff, sys::VIR_DOMAIN_CRASHED => DomainState::Crashed, sys::VIR_DOMAIN_PMSUSPENDED => DomainState::PowerManagementSuspended, _ => DomainState::Other, }) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct StartDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: StartDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Start domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.create()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct ShutdownDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: ShutdownDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Shutdown domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.shutdown()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct KillDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: KillDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Kill domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.destroy()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct ResetDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: ResetDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Reset domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.reset()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct SuspendDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: SuspendDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Suspend domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.suspend()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct ResumeDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: ResumeDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Resume domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.resume()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result>")] pub struct ScreenshotDomainReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result>; fn handle(&mut self, msg: ScreenshotDomainReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Take screenshot of domain: {}", msg.0.as_string()); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let stream = Stream::new(&self.m, 0)?; domain.screenshot(&stream, 0, 0)?; let mut screen_out = Vec::with_capacity(1000000); let mut buff = [0u8; 1000]; loop { let size = stream.recv(&mut buff)?; if size == 0 { break; } screen_out.extend_from_slice(&buff[0..size]); } let image = image::load_from_memory(&screen_out)?; let mut png_out = Cursor::new(Vec::new()); image.write_to(&mut png_out, ImageOutputFormat::Png)?; Ok(png_out.into_inner()) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct IsDomainAutostart(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: IsDomainAutostart, _ctx: &mut Self::Context) -> Self::Result { log::debug!( "Check if autostart is enabled for a domain: {}", msg.0.as_string() ); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; Ok(domain.get_autostart()?) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct SetDomainAutostart(pub XMLUuid, pub bool); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: SetDomainAutostart, _ctx: &mut Self::Context) -> Self::Result { log::debug!( "Set autostart enabled={} for a domain: {}", msg.1, msg.0.as_string() ); let domain = Domain::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; domain.set_autostart(msg.1)?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct DefineNetwork(pub NetworkInfo, pub NetworkXML); impl Handler for LibVirtActor { type Result = anyhow::Result; 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.1); let network_xml = msg.1.into_xml()?; log::debug!("Define network XML: {network_xml}"); let network = Network::define_xml(&self.m, &network_xml)?; 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) } } #[derive(Message)] #[rtype(result = "anyhow::Result>")] pub struct GetNetworksListReq; impl Handler for LibVirtActor { type Result = anyhow::Result>; fn handle(&mut self, _msg: GetNetworksListReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get full list of networks"); let networks = self.m.list_all_networks(0)?; let mut ids = Vec::with_capacity(networks.len()); for d in networks { ids.push(XMLUuid::parse_from_str(&d.get_uuid_string()?)?); } Ok(ids) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetNetworkXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetNetworkXMLReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get network XML:\n{}", msg.0.as_string()); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let xml = network.get_xml_desc(0)?; log::debug!("XML = {}", xml); Ok(serde_xml_rs::from_str(&xml)?) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetSourceNetworkXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetSourceNetworkXMLReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get network XML:\n{}", msg.0.as_string()); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; Ok(network.get_xml_desc(0)?) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct DeleteNetwork(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; 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(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct IsNetworkAutostart(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: IsNetworkAutostart, _ctx: &mut Self::Context) -> Self::Result { log::debug!( "Check if autostart is enabled for a network: {}", msg.0.as_string() ); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; Ok(network.get_autostart()?) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct SetNetworkAutostart(pub XMLUuid, pub bool); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: SetNetworkAutostart, _ctx: &mut Self::Context) -> Self::Result { log::debug!( "Set autostart enabled={} for a network: {}", msg.1, msg.0.as_string() ); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; network.set_autostart(msg.1)?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct IsNetworkStarted(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: IsNetworkStarted, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Check if a network is started: {}", msg.0.as_string()); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; Ok(network.is_active()?) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct StartNetwork(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: StartNetwork, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Start a network: {}", msg.0.as_string()); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; network.create()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result<()>")] pub struct StopNetwork(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result<()>; fn handle(&mut self, msg: StopNetwork, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Stop a network: {}", msg.0.as_string()); let network = Network::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; network.destroy()?; Ok(()) } } #[derive(Message)] #[rtype(result = "anyhow::Result>")] pub struct GetNWFiltersListReq; impl Handler for LibVirtActor { type Result = anyhow::Result>; fn handle(&mut self, _msg: GetNWFiltersListReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get full list of network filters"); let networks = self.m.list_all_nw_filters(0)?; let mut ids = Vec::with_capacity(networks.len()); for d in networks { ids.push(XMLUuid::parse_from_str(&d.get_uuid_string()?)?); } Ok(ids) } } #[derive(Message)] #[rtype(result = "anyhow::Result")] pub struct GetNWFilterXMLReq(pub XMLUuid); impl Handler for LibVirtActor { type Result = anyhow::Result; fn handle(&mut self, msg: GetNWFilterXMLReq, _ctx: &mut Self::Context) -> Self::Result { log::debug!("Get network filter XML:\n{}", msg.0.as_string()); let filter = NWFilter::lookup_by_uuid_string(&self.m, &msg.0.as_string())?; let xml = filter.get_xml_desc(0)?; log::debug!("XML = {}", xml); NetworkFilterXML::parse_xml(xml) } }