changeset 50:0376dbfaa7ed

Merge branch 'support_backingStore' into 'master' Support backing store See merge request ie-syskan/ie-virsh!1
author 清水 隆博 <k198584@ie.u-ryukyu.ac.jp>
date Sun, 22 Nov 2020 18:44:45 +0900
parents ae7ba39dfcbe (current diff) 871a98179dfe (diff)
children 9590804117b7
files
diffstat 5 files changed, 212 insertions(+), 91 deletions(-) [+]
line wrap: on
line diff
--- 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::<u8>().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<String> {
--- 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<String>,
 }
 
 /// 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(_) => {
--- 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::<String>();
         // e195730 -> e19, tnal -> teacher
-        let grade = if year.parse::<u8>().is_ok() {
+        let affilication = if year.parse::<u8>().is_ok() {
             user.name.chars().take(3).collect::<String>()
         } 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)
     }
 }
 
--- 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<String> {
+    let template_path = format!(
+        "{}{}{}{}",
+        TEMPLATE_DIR, TEMPLATE_SUFFIX, &template_name, TEMPLATE_FILE_EXTENSION
+    );
+    if Path::new(&template_path).exists() {
+        return Some(template_path);
+    }
+    None
+}
--- 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<String, Error> {
     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::<String>();
-        let affilication = if year.parse::<u8>().is_ok() {
-            // /etc/libvirt/qemu/e19/e195729
-            user_name.chars().take(3).collect::<String>()
-        } else {
-            "teacher".to_string()
-        };
+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
+        );
 
-        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<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_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<String, Error> {
         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() => {