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()
}