# HG changeset patch # User 清水 隆博 # Date 1606038285 -32400 # Node ID 0376dbfaa7ed7549544f1238d2890011fd9eaedb # Parent ae7ba39dfcbe597fa2d7181fbb1ca5444200888a# Parent 871a98179dfe3a8e2e3ae7e9591e0a107019637e Merge branch 'support_backingStore' into 'master' Support backing store See merge request ie-syskan/ie-virsh!1 diff -r ae7ba39dfcbe -r 0376dbfaa7ed src/command.rs --- a/src/command.rs Sat Nov 21 17:14:56 2020 +0900 +++ b/src/command.rs Sun Nov 22 18:44:45 2020 +0900 @@ -4,6 +4,8 @@ use std::io; +const RENTAL_DIR: &str = "/ie-ryukyu/kvm/images/rental"; + pub fn list(user_name: &str) { let (ldump_msg, vm_list_strs) = virsh::get_vm_list(user_name); println!("{}", ldump_msg.info_msg); @@ -23,64 +25,57 @@ Ok(()) } -pub fn define(user: &user::User, vm_name: String) { - let vm_name = generate_vm_name(&user.name, &vm_name); - let vm_arg = xml::GenerateVMArg::new(&user.name, &vm_name, false); - if let Ok(xml_path) = vm_arg.generate() { - virsh::command_require_vm_name(xml_path, "define") +pub fn define(user_detail: &user::UserDetail, vm_name: &str) { + let vm_name = generate_vm_name(&user_detail.user.name, &vm_name); + let builder = xml::Builder::new(&user_detail, &vm_name).finalize(); + if let Ok(xml_path) = builder.generate() { + virsh::command_require_vm_name(&xml_path, "define") } } -pub fn start(user_name: &str, vm_name: String) { - virsh::command_require_vm_name(get_vm_name(user_name, &vm_name), "start"); -} - -pub fn shutdown(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "shutdown") -} - -pub fn destroy(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "destroy") -} - -pub fn console(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "console") -} - -pub fn vncdisplay(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "vncdisplay") +pub fn define_from_template(user_detail: &user::UserDetail, vm_name: &str, template: &str) { + let vm_name = generate_vm_name(&user_detail.user.name, &vm_name); + println!("{}", vm_name); + let backing_path = match virsh::get_backing_path(template) { + Some(path) => path, + None => { + println!("not found {}", template); + return; + } + }; + let builder = xml::Builder::new(&user_detail, &vm_name) + .backing(&backing_path) + .qcow2_dir(RENTAL_DIR) + .finalize(); + let xml_path = match builder.generate() { + Ok(xml_path) => xml_path, + Err(e) => { + println!("failed generate xml: {}", e); + return; + } + }; + virsh::generate_qemu_from_template(RENTAL_DIR, &vm_name, &backing_path); + virsh::command_require_vm_name(&xml_path, "define"); } -pub fn ttyconsole(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "ttyconsole") -} - -pub fn dumpxml(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "dumpxml") +pub fn start(user_name: &str, vm_name: &str) { + virsh::command_require_vm_name(&get_vm_name(user_name, vm_name), "start"); } -pub fn undefine(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "undefine") +pub fn common(user_name: &str, vm_name: &str, command: &str) { + exec_cmd_from_name_or_id(user_name, vm_name, command) } -pub fn domiflist(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "domiflist") -} - -pub fn dominfo(user_name: &str, vm_name: String) { - exec_cmd_from_name_or_id(user_name, &vm_name, "dominfo") -} - -pub fn dumpvncpasswd(user_name: &str, vm_name: String) {} +pub fn dumpvncpasswd(_user_name: &str, _vm_name: &str) {} fn exec_cmd_from_name_or_id(user_name: &str, name_or_id: &str, command: &str) { if name_or_id.parse::().is_ok() { let vm_name_or_none = get_vm_name_from_id(user_name, &name_or_id); if let Some(vm_name) = vm_name_or_none { - return virsh::command_require_vm_name(vm_name, command); + return virsh::command_require_vm_name(&vm_name, command); } } - virsh::command_require_vm_name(get_vm_name(user_name, name_or_id), command); + virsh::command_require_vm_name(&get_vm_name(user_name, name_or_id), command); } fn get_vm_name_from_id(user_name: &str, arg_id: &str) -> Option { diff -r ae7ba39dfcbe -r 0376dbfaa7ed src/main.rs --- a/src/main.rs Sat Nov 21 17:14:56 2020 +0900 +++ b/src/main.rs Sun Nov 22 18:44:45 2020 +0900 @@ -40,6 +40,9 @@ #[derive(Clap)] struct Define { name: String, + /// A level of verbosity, and can be used multiple times + #[clap(short, long, parse(from_str))] + template: Option, } /// domain information in XML @@ -122,7 +125,7 @@ SubCommand::Start(arg) => { user::set_root_id(); - command::start(&user_name, arg.name); + command::start(&user_name, &arg.name); } SubCommand::Define(arg) => { @@ -132,52 +135,59 @@ gid, name: user_name, }; - command::define(&user, arg.name); + + let userdetail = user::UserDetail::new(&user); + + if let Some(template) = arg.template { + command::define_from_template(&userdetail, &arg.name, &template); + return; + } + command::define(&userdetail, &arg.name); } SubCommand::Shutdown(arg) => { user::set_root_id(); - command::shutdown(&user_name, arg.name); + command::common(&user_name, &arg.name, "shutdown"); } SubCommand::Console(arg) => { user::set_root_id(); - command::console(&user_name, arg.name); + command::common(&user_name, &arg.name, "console"); } SubCommand::Destroy(arg) => { user::set_root_id(); - command::destroy(&user_name, arg.name); + command::common(&user_name, &arg.name, "destroy"); } SubCommand::Vncdisplay(arg) => { user::set_root_id(); - command::vncdisplay(&user_name, arg.name); + command::common(&user_name, &arg.name, "vncdisplay"); } SubCommand::Ttyconsole(arg) => { user::set_root_id(); - command::ttyconsole(&user_name, arg.name); + command::common(&user_name, &arg.name, "ttyconsole"); } SubCommand::Dumpxml(arg) => { user::set_root_id(); - command::dumpxml(&user_name, arg.name); + command::common(&user_name, &arg.name, "dumpxml"); } SubCommand::Undefine(arg) => { user::set_root_id(); - command::undefine(&user_name, arg.name); + command::common(&user_name, &arg.name, "undefine"); } SubCommand::Domiflist(arg) => { user::set_root_id(); - command::domiflist(&user_name, arg.name); + command::common(&user_name, &arg.name, "domiflist"); } SubCommand::Dominfo(arg) => { user::set_root_id(); - command::dominfo(&user_name, arg.name); + command::common(&user_name, &arg.name, "dominfo"); } SubCommand::Templates(_) => { diff -r ae7ba39dfcbe -r 0376dbfaa7ed src/user.rs --- a/src/user.rs Sat Nov 21 17:14:56 2020 +0900 +++ b/src/user.rs Sun Nov 22 18:44:45 2020 +0900 @@ -6,7 +6,8 @@ pub struct UserDetail<'a> { pub user: &'a User, - pub grade: String, + pub year: String, + pub affilication: String, } impl<'a> UserDetail<'a> { @@ -14,16 +15,20 @@ // e195730 -> 19 let year = user.name.chars().skip(1).take(2).collect::(); // e195730 -> e19, tnal -> teacher - let grade = if year.parse::().is_ok() { + let affilication = if year.parse::().is_ok() { user.name.chars().take(3).collect::() } else { "teacher".to_string() }; - UserDetail { user, grade } + UserDetail { + user, + year, + affilication, + } } pub fn getpass(self) -> String { - format!("{}/{}", self.grade, &self.user.name) + format!("{}/{}", self.affilication, &self.user.name) } } diff -r ae7ba39dfcbe -r 0376dbfaa7ed src/virsh.rs --- a/src/virsh.rs Sat Nov 21 17:14:56 2020 +0900 +++ b/src/virsh.rs Sun Nov 22 18:44:45 2020 +0900 @@ -1,11 +1,14 @@ use std::io::{self, Write}; use std::process::Command; +use std::env; use std::fs; +use std::path::Path; const TEMPLATE_DIR: &str = "/ie-ryukyu/kvm/images/templates/"; const TEMPLATE_SUFFIX: &str = "template-"; const TEMPLATE_FILE_EXTENSION: &str = ".qcow2"; +const QCOW2: &str = "qcow2"; pub struct ListDumpMsg { pub info_msg: String, @@ -55,7 +58,7 @@ ) } -pub fn command_require_vm_name(vm_name: String, operation: &str) { +pub fn command_require_vm_name(vm_name: &str, operation: &str) { let output = Command::new("virsh") .arg(operation) .arg(vm_name) @@ -65,3 +68,36 @@ io::stdout().write_all(&output.stdout).unwrap(); io::stderr().write_all(&output.stderr).unwrap(); } + +pub fn generate_qemu_from_template(vm_path_string: &str, vm_name: &str, template_path: &str) { + let vm_path = Path::new(&vm_path_string); + let ok = env::set_current_dir(&vm_path); + if ok.is_err() { + println!("failed cd at {}", &vm_path_string); + return; + } + let output = Command::new("qemu-img") + .arg("create") + .arg("-F") + .arg(QCOW2) + .arg("-b") + .arg(template_path) + .arg("-f") + .arg(QCOW2) + .arg(format!("{}.qcow2", vm_name)) + .output() + .unwrap_or_else(|_| panic!("failed to generate {}", &vm_name)); + io::stdout().write_all(&output.stdout).unwrap(); + io::stderr().write_all(&output.stderr).unwrap(); +} + +pub fn get_backing_path(template_name: &str) -> Option { + let template_path = format!( + "{}{}{}{}", + TEMPLATE_DIR, TEMPLATE_SUFFIX, &template_name, TEMPLATE_FILE_EXTENSION + ); + if Path::new(&template_path).exists() { + return Some(template_path); + } + None +} diff -r ae7ba39dfcbe -r 0376dbfaa7ed src/xml.rs --- a/src/xml.rs Sat Nov 21 17:14:56 2020 +0900 +++ b/src/xml.rs Sun Nov 22 18:44:45 2020 +0900 @@ -31,15 +31,6 @@ const LIBVIRT_XML_DIR: &str = "/etc/libvirt/qemu"; const QCOW2_PATH: &str = "/mnt/ie-virsh"; -pub struct GenerateVMArg { - vm_name: String, - qcow2_path: String, - xml_path: String, - vnc_password: String, - is_debug: bool, - tcp_port: u64, -} - pub fn dump_vnc_passwd(user: user::UserDetail, _vm_name: &str) -> Result { let user_pass = user.getpass(); let mut reader = Reader::from_reader(BufReader::new(File::open(get_xml_dir(&user_pass))?)); @@ -59,43 +50,96 @@ format!("{}/{}", LIBVIRT_XML_DIR, user_path) } -impl GenerateVMArg { - pub fn new(user_name: &str, vm_name: &str, is_debug: bool) -> GenerateVMArg { - let year = user_name.chars().skip(1).take(2).collect::(); - let affilication = if year.parse::().is_ok() { - // /etc/libvirt/qemu/e19/e195729 - user_name.chars().take(3).collect::() - } else { - "teacher".to_string() - }; +pub struct Builder { + vm_name: String, + debug_tcp_port: Option, + 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, + 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 + ); - let xml_dir = format!("{}/{}/{}", LIBVIRT_XML_DIR, affilication, user_name); - let xml_path = format!("{}/{}.xml", xml_dir, vm_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 + } - if !Path::new(&xml_dir).exists() { - fs::create_dir_all(xml_dir).ok(); + 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) -> &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_dir = format!("{}/{}/{}", QCOW2_PATH, affilication, user_name); + let qcow2_path = format!("{}/{}.qcow2", &self.qcow2_dir, &self.vm_name); - let qcow2_path = format!("{}/{}.qcow2", qcow2_dir, vm_name); - - if !Path::new(&qcow2_dir).exists() { - fs::create_dir_all(qcow2_dir).ok(); + if !Path::new(&self.qcow2_dir).exists() { + fs::create_dir_all(&self.qcow2_dir).ok(); } let pw = generate_pw(); - GenerateVMArg { - vm_name: vm_name.to_string(), + GenerateVM { + vm_name: self.vm_name.clone(), qcow2_path, xml_path, vnc_password: pw, - is_debug, - tcp_port: 90, + 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 { let mut reader = Reader::from_reader(BufReader::new(File::open(TEMPLATE_XML_FILE)?)); @@ -117,7 +161,7 @@ } Ok(Event::Start(ref e)) - if (e.name() == ROOT_START_TAG.as_bytes() && self.is_debug) => + 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); @@ -137,7 +181,7 @@ } let mut qemu_elem = BytesStart::borrowed_name(QEMU_ARG_TAG.as_bytes()); - let gdb_port: &str = &format!("tcp::{}", self.tcp_port); + 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(); @@ -155,17 +199,48 @@ 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"source"); + 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() => {