Mercurial > hg > Members > anatofuz > ie-virsh
view src/xml.rs @ 47:9a7ceb63efbc
fix emit xml
author | AnaTofuZ <anatofuz@gmail.com> |
---|---|
date | Sun, 22 Nov 2020 18:30:16 +0900 |
parents | 868d51208aa3 |
children | 871a98179dfe |
line wrap: on
line source
use super::user; use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; use quick_xml::{Reader, Writer}; use rand::Rng; use std::fs; use std::fs::File; use std::io::{BufReader, BufWriter, Error}; use std::path::Path; use uuid::Uuid; const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ abcdefghijklmnopqrstuvwxyz\ 0123456789)(*^%$#@!~"; const PASSWORD_LEN: usize = 30; const DOMAIN_XMLNS_QEMU: (&str, &str) = ("xmlns:qemu", "http://libvirt.org/schemas/domain/qemu/1.0"); const IE_VIRSH_TEMPLATE_VM_NAME: &str = "ie-virsh-template"; const VNC_XML_TAG: &str = "graphics"; const ROOT_START_TAG: &str = "domain"; const QEMU_COMMAND_LINE_TAG: &str = "qemu:commandline"; const QEMU_ARG_TAG: &str = "qemu:arg"; const TEMPLATE_XML_FILE: &str = "/etc/libvirt/template.xml"; const LIBVIRT_XML_DIR: &str = "/etc/libvirt/qemu"; const QCOW2_PATH: &str = "/mnt/ie-virsh"; pub fn dump_vnc_passwd(user: user::UserDetail, _vm_name: &str) -> Result<String, Error> { let user_pass = user.getpass(); let mut reader = Reader::from_reader(BufReader::new(File::open(get_xml_dir(&user_pass))?)); let mut buf = Vec::new(); loop { match reader.read_event(&mut buf) { Ok(Event::Eof) => break, Ok(Event::Empty(ref e)) if e.name() == VNC_XML_TAG.as_bytes() => {} Ok(_) => {} Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), } } Ok(String::from("ok")) } fn get_xml_dir(user_path: &str) -> String { format!("{}/{}", LIBVIRT_XML_DIR, user_path) } pub struct Builder { vm_name: String, debug_tcp_port: Option<u64>, backing_file: String, is_backing: bool, xml_dir: String, qcow2_dir: String, } pub struct GenerateVM { vm_name: String, qcow2_path: String, xml_path: String, vnc_password: String, debug_tcp_port: Option<u64>, backing_file: String, is_backing: bool, } impl Builder { pub fn new(user_detail: &user::UserDetail, vm_name: &str) -> Builder { let xml_dir = format!( "{}/{}/{}", LIBVIRT_XML_DIR, &user_detail.affilication, &user_detail.user.name ); let qcow2_dir = format!( "{}/{}/{}", QCOW2_PATH, &user_detail.affilication, &user_detail.user.name ); Builder { vm_name: vm_name.to_string(), debug_tcp_port: None, backing_file: "".to_string(), is_backing: false, xml_dir, qcow2_dir, } } pub fn xml_dir(&mut self, xml_dir: &str) -> &mut Builder { self.xml_dir = xml_dir.to_string(); self } pub fn qcow2_dir(&mut self, qcow2_dir: &str) -> &mut Builder { self.qcow2_dir = qcow2_dir.to_string(); self } pub fn debug_tcp_port(&mut self, port: Option<u64>) -> &mut Builder { self.debug_tcp_port = port; self } pub fn backing(&mut self, backing_file: &str) -> &mut Builder { self.backing_file = backing_file.to_string(); self.is_backing = true; self } pub fn finalize(&self) -> GenerateVM { let xml_path = format!("{}/{}.xml", &self.xml_dir, &self.vm_name); if !Path::new(&self.xml_dir).exists() { fs::create_dir_all(&self.xml_dir).ok(); } let qcow2_path = format!("{}/{}.qcow2", &self.qcow2_dir, &self.vm_name); if !Path::new(&self.qcow2_dir).exists() { fs::create_dir_all(&self.qcow2_dir).ok(); } let pw = generate_pw(); GenerateVM { vm_name: self.vm_name.clone(), qcow2_path, xml_path, vnc_password: pw, debug_tcp_port: self.debug_tcp_port, backing_file: self.backing_file.clone(), is_backing: self.is_backing, } } } impl GenerateVM { pub fn generate(self) -> Result<String, Error> { let mut reader = Reader::from_reader(BufReader::new(File::open(TEMPLATE_XML_FILE)?)); println!("generate xml :{}", self.xml_path); let mut writer = Writer::new(BufWriter::new(File::create(self.xml_path.clone()).unwrap())); let mut buf = Vec::new(); loop { match reader.read_event(&mut buf) { Ok(Event::Start(ref e)) if e.name() == b"uuid" => { writer .write_event(Event::Start(e.clone())) .expect("faild write event"); reader .read_event(&mut Vec::new()) .expect("faild read event"); let vm_uuid = Uuid::new_v4().to_string(); let elem = BytesText::from_plain_str(&vm_uuid); writer.write_event(Event::Text(elem)).unwrap(); } Ok(Event::Start(ref e)) if (e.name() == ROOT_START_TAG.as_bytes() && self.debug_tcp_port.is_some()) => { let mut elem = e.clone(); elem.push_attribute(DOMAIN_XMLNS_QEMU); writer.write_event(Event::Start(elem)).unwrap(); let qemu_command_line_start = BytesStart::borrowed_name(QEMU_COMMAND_LINE_TAG.as_bytes()); writer .write_event(Event::Start(qemu_command_line_start)) .unwrap(); for value in ["-S", "-gdb"].iter() { let mut qemu_elem = BytesStart::borrowed_name(QEMU_ARG_TAG.as_bytes()); let v: &str = &value; qemu_elem.push_attribute(("value", v)); writer.write_event(Event::Empty(qemu_elem)).unwrap(); } let mut qemu_elem = BytesStart::borrowed_name(QEMU_ARG_TAG.as_bytes()); let gdb_port: &str = &format!("tcp::{}", self.debug_tcp_port.unwrap()); qemu_elem.push_attribute(("value", gdb_port)); writer.write_event(Event::Empty(qemu_elem)).unwrap(); let qemu_command_line_end = BytesEnd::borrowed(QEMU_COMMAND_LINE_TAG.as_bytes()); writer .write_event(Event::End(qemu_command_line_end)) .unwrap(); } Ok(Event::Empty(ref e)) if e.name() == VNC_XML_TAG.as_bytes() => { let mut elem = e.clone(); let pw: &str = &self.vnc_password; elem.push_attribute(("passwd", pw)); writer.write_event(Event::Empty(elem)).ok(); } // replace qcow2 file Ok(Event::Empty(ref e)) if (e.name() == b"source") => { let mut elem = e.clone(); let is_qcow_file = elem .attributes() .find(|attr| attr.as_ref().unwrap().key == b"file"); let mut is_qcow_elem = false; if is_qcow_file.is_some() { elem.clear_attributes(); let qcow2_path: &str = &self.qcow2_path; elem.push_attribute(("file", qcow2_path)); is_qcow_elem = true; } writer.write_event(Event::Empty(elem)).ok(); // use template qcow2 if is_qcow_elem && self.is_backing { writer.write(b"\n").unwrap(); let mut backing_store_start = BytesStart::borrowed_name(b"backingStore"); backing_store_start.push_attribute(("type", "file")); backing_store_start.push_attribute(("index", "3")); writer .write_event(Event::Start(backing_store_start)) .unwrap(); writer.write(b"\n").unwrap(); let mut format_elem = BytesStart::borrowed_name(b"format"); format_elem.push_attribute(("type", "qcow2")); writer.write_event(Event::Empty(format_elem)).unwrap(); writer.write(b"\n").unwrap(); let mut backing_sorce = BytesStart::borrowed_name(b"sorce"); let backing_file: &str = &self.backing_file; backing_sorce.push_attribute(("file", backing_file)); writer.write_event(Event::Empty(backing_sorce)).unwrap(); writer.write(b"\n").unwrap(); let backing_store_end = BytesEnd::borrowed(b"backingStore"); writer.write_event(Event::End(backing_store_end)).unwrap(); writer.write(b"\n").unwrap(); } } Ok(Event::Text(ref e)) if e.escaped() == IE_VIRSH_TEMPLATE_VM_NAME.as_bytes() => { let elem = BytesText::from_plain_str(&self.vm_name); writer.write_event(Event::Text(elem)).unwrap(); } Ok(Event::Eof) => break, // you can use either `e` or `&e` if you don't want to move the event Ok(e) => assert!(writer.write_event(&e).is_ok()), Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e), } buf.clear(); } println!("generate xml : {}", self.xml_path); println!("vnc password : {}", self.vnc_password); Ok(self.xml_path) } } fn generate_pw() -> String { let mut rng = rand::thread_rng(); (0..PASSWORD_LEN) .map(|_| { let idx = rng.gen_range(0, CHARSET.len()); CHARSET[idx] as char }) .collect() }