26
|
1 use super::user;
|
|
2
|
19
|
3 use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
|
4 use quick_xml::{Reader, Writer};
|
|
5 use rand::Rng;
|
20
|
6 use std::fs;
|
19
|
7 use std::fs::File;
|
20
|
8 use std::io::{BufReader, BufWriter, Error};
|
19
|
9 use std::path::Path;
|
|
10 use uuid::Uuid;
|
|
11
|
|
12 const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
|
|
13 abcdefghijklmnopqrstuvwxyz\
|
20
|
14 0123456789)(*^%$#@!~";
|
19
|
15
|
|
16 const PASSWORD_LEN: usize = 30;
|
|
17
|
|
18 const DOMAIN_XMLNS_QEMU: (&str, &str) =
|
|
19 ("xmlns:qemu", "http://libvirt.org/schemas/domain/qemu/1.0");
|
|
20
|
29
|
21 const IE_VIRSH_TEMPLATE_VM_NAME: &str = "ie-virsh-template";
|
|
22 const VNC_XML_TAG: &str = "graphics";
|
19
|
23
|
29
|
24 const ROOT_START_TAG: &str = "domain";
|
19
|
25
|
29
|
26 const QEMU_COMMAND_LINE_TAG: &str = "qemu:commandline";
|
|
27 const QEMU_ARG_TAG: &str = "qemu:arg";
|
19
|
28
|
20
|
29 const TEMPLATE_XML_FILE: &str = "/etc/libvirt/template.xml";
|
|
30
|
|
31 const LIBVIRT_XML_DIR: &str = "/etc/libvirt/qemu";
|
|
32 const QCOW2_PATH: &str = "/mnt/ie-virsh";
|
|
33
|
26
|
34 pub fn dump_vnc_passwd(user: user::UserDetail, _vm_name: &str) -> Result<String, Error> {
|
|
35 let user_pass = user.getpass();
|
|
36 let mut reader = Reader::from_reader(BufReader::new(File::open(get_xml_dir(&user_pass))?));
|
|
37 let mut buf = Vec::new();
|
|
38 loop {
|
|
39 match reader.read_event(&mut buf) {
|
|
40 Ok(Event::Eof) => break,
|
29
|
41 Ok(Event::Empty(ref e)) if e.name() == VNC_XML_TAG.as_bytes() => {}
|
26
|
42 Ok(_) => {}
|
|
43 Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
|
|
44 }
|
|
45 }
|
|
46 Ok(String::from("ok"))
|
|
47 }
|
|
48
|
|
49 fn get_xml_dir(user_path: &str) -> String {
|
|
50 format!("{}/{}", LIBVIRT_XML_DIR, user_path)
|
20
|
51 }
|
|
52
|
35
|
53 pub struct Builder {
|
|
54 vm_name: String,
|
|
55 debug_tcp_port: Option<u64>,
|
39
|
56 backing_file: String,
|
|
57 is_backing: bool,
|
41
|
58 xml_dir: String,
|
|
59 qcow2_dir: String,
|
35
|
60 }
|
|
61
|
|
62 pub struct GenerateVM {
|
|
63 vm_name: String,
|
|
64 qcow2_path: String,
|
|
65 xml_path: String,
|
|
66 vnc_password: String,
|
|
67 debug_tcp_port: Option<u64>,
|
39
|
68 backing_file: String,
|
|
69 is_backing: bool,
|
35
|
70 }
|
|
71
|
|
72 impl Builder {
|
41
|
73 pub fn new(user_detail: &user::UserDetail, vm_name: &str) -> Builder {
|
|
74 let xml_dir = format!(
|
|
75 "{}/{}/{}",
|
42
|
76 LIBVIRT_XML_DIR, &user_detail.affilication, &user_detail.user.name
|
41
|
77 );
|
|
78
|
|
79 let qcow2_dir = format!(
|
|
80 "{}/{}/{}",
|
42
|
81 QCOW2_PATH, &user_detail.affilication, &user_detail.user.name
|
41
|
82 );
|
|
83
|
35
|
84 Builder {
|
36
|
85 vm_name: vm_name.to_string(),
|
35
|
86 debug_tcp_port: None,
|
39
|
87 backing_file: "".to_string(),
|
|
88 is_backing: false,
|
41
|
89 xml_dir,
|
|
90 qcow2_dir,
|
35
|
91 }
|
|
92 }
|
|
93
|
42
|
94 pub fn xml_dir(&mut self, xml_dir: &str) -> &mut Builder {
|
|
95 self.xml_dir = xml_dir.to_string();
|
|
96 self
|
|
97 }
|
|
98
|
|
99 pub fn qcow2_dir(&mut self, qcow2_dir: &str) -> &mut Builder {
|
|
100 self.qcow2_dir = qcow2_dir.to_string();
|
|
101 self
|
|
102 }
|
|
103
|
35
|
104 pub fn debug_tcp_port(&mut self, port: Option<u64>) -> &mut Builder {
|
|
105 self.debug_tcp_port = port;
|
|
106 self
|
|
107 }
|
|
108
|
39
|
109 pub fn backing(&mut self, backing_file: &str) -> &mut Builder {
|
|
110 self.backing_file = backing_file.to_string();
|
44
|
111 self.is_backing = true;
|
35
|
112 self
|
|
113 }
|
|
114
|
39
|
115 pub fn finalize(&self) -> GenerateVM {
|
42
|
116 let xml_path = format!("{}/{}.xml", &self.xml_dir, &self.vm_name);
|
20
|
117
|
41
|
118 if !Path::new(&self.xml_dir).exists() {
|
|
119 fs::create_dir_all(&self.xml_dir).ok();
|
20
|
120 }
|
|
121
|
42
|
122 let qcow2_path = format!("{}/{}.qcow2", &self.qcow2_dir, &self.vm_name);
|
20
|
123
|
41
|
124 if !Path::new(&self.qcow2_dir).exists() {
|
|
125 fs::create_dir_all(&self.qcow2_dir).ok();
|
20
|
126 }
|
|
127
|
|
128 let pw = generate_pw();
|
39
|
129
|
35
|
130 GenerateVM {
|
|
131 vm_name: self.vm_name.clone(),
|
26
|
132 qcow2_path,
|
|
133 xml_path,
|
|
134 vnc_password: pw,
|
35
|
135 debug_tcp_port: self.debug_tcp_port,
|
39
|
136 backing_file: self.backing_file.clone(),
|
|
137 is_backing: self.is_backing,
|
20
|
138 }
|
|
139 }
|
35
|
140 }
|
19
|
141
|
35
|
142 impl GenerateVM {
|
21
|
143 pub fn generate(self) -> Result<String, Error> {
|
20
|
144 let mut reader = Reader::from_reader(BufReader::new(File::open(TEMPLATE_XML_FILE)?));
|
19
|
145
|
26
|
146 println!("generate xml :{}", self.xml_path);
|
|
147 let mut writer = Writer::new(BufWriter::new(File::create(self.xml_path.clone()).unwrap()));
|
20
|
148 let mut buf = Vec::new();
|
|
149 loop {
|
|
150 match reader.read_event(&mut buf) {
|
|
151 Ok(Event::Start(ref e)) if e.name() == b"uuid" => {
|
|
152 writer
|
|
153 .write_event(Event::Start(e.clone()))
|
|
154 .expect("faild write event");
|
|
155 reader
|
|
156 .read_event(&mut Vec::new())
|
|
157 .expect("faild read event");
|
|
158 let vm_uuid = Uuid::new_v4().to_string();
|
|
159 let elem = BytesText::from_plain_str(&vm_uuid);
|
|
160 writer.write_event(Event::Text(elem)).unwrap();
|
19
|
161 }
|
|
162
|
29
|
163 Ok(Event::Start(ref e))
|
33
|
164 if (e.name() == ROOT_START_TAG.as_bytes() && self.debug_tcp_port.is_some()) =>
|
29
|
165 {
|
20
|
166 let mut elem = e.clone();
|
|
167 elem.push_attribute(DOMAIN_XMLNS_QEMU);
|
|
168 writer.write_event(Event::Start(elem)).unwrap();
|
|
169
|
29
|
170 let qemu_command_line_start =
|
|
171 BytesStart::borrowed_name(QEMU_COMMAND_LINE_TAG.as_bytes());
|
20
|
172 writer
|
|
173 .write_event(Event::Start(qemu_command_line_start))
|
|
174 .unwrap();
|
19
|
175
|
26
|
176 for value in ["-S", "-gdb"].iter() {
|
29
|
177 let mut qemu_elem = BytesStart::borrowed_name(QEMU_ARG_TAG.as_bytes());
|
20
|
178 let v: &str = &value;
|
|
179 qemu_elem.push_attribute(("value", v));
|
|
180 writer.write_event(Event::Empty(qemu_elem)).unwrap();
|
|
181 }
|
19
|
182
|
29
|
183 let mut qemu_elem = BytesStart::borrowed_name(QEMU_ARG_TAG.as_bytes());
|
33
|
184 let gdb_port: &str = &format!("tcp::{}", self.debug_tcp_port.unwrap());
|
26
|
185 qemu_elem.push_attribute(("value", gdb_port));
|
|
186 writer.write_event(Event::Empty(qemu_elem)).unwrap();
|
|
187
|
29
|
188 let qemu_command_line_end =
|
|
189 BytesEnd::borrowed(QEMU_COMMAND_LINE_TAG.as_bytes());
|
20
|
190 writer
|
|
191 .write_event(Event::End(qemu_command_line_end))
|
|
192 .unwrap();
|
19
|
193 }
|
20
|
194
|
29
|
195 Ok(Event::Empty(ref e)) if e.name() == VNC_XML_TAG.as_bytes() => {
|
20
|
196 let mut elem = e.clone();
|
26
|
197 let pw: &str = &self.vnc_password;
|
20
|
198 elem.push_attribute(("passwd", pw));
|
|
199 writer.write_event(Event::Empty(elem)).ok();
|
|
200 }
|
19
|
201
|
34
|
202 // replace qcow2 file
|
20
|
203 Ok(Event::Empty(ref e)) if (e.name() == b"source") => {
|
|
204 let mut elem = e.clone();
|
|
205 let is_qcow_file = elem
|
|
206 .attributes()
|
|
207 .find(|attr| attr.as_ref().unwrap().key == b"file");
|
46
|
208 let mut is_qcow_elem = false;
|
20
|
209 if is_qcow_file.is_some() {
|
|
210 elem.clear_attributes();
|
26
|
211 let qcow2_path: &str = &self.qcow2_path;
|
20
|
212 elem.push_attribute(("file", qcow2_path));
|
46
|
213 is_qcow_elem = true;
|
20
|
214 }
|
|
215 writer.write_event(Event::Empty(elem)).ok();
|
44
|
216
|
|
217 // use template qcow2
|
46
|
218 if is_qcow_elem && self.is_backing {
|
47
|
219 writer.write(b"\n").unwrap();
|
44
|
220 let mut backing_store_start = BytesStart::borrowed_name(b"backingStore");
|
|
221 backing_store_start.push_attribute(("type", "file"));
|
|
222 backing_store_start.push_attribute(("index", "3"));
|
|
223 writer
|
47
|
224 .write_event(Event::Start(backing_store_start))
|
44
|
225 .unwrap();
|
|
226
|
|
227 writer.write(b"\n").unwrap();
|
|
228
|
|
229 let mut format_elem = BytesStart::borrowed_name(b"format");
|
|
230 format_elem.push_attribute(("type", "qcow2"));
|
|
231 writer.write_event(Event::Empty(format_elem)).unwrap();
|
|
232 writer.write(b"\n").unwrap();
|
|
233
|
|
234 let mut backing_sorce = BytesStart::borrowed_name(b"sorce");
|
|
235 let backing_file: &str = &self.backing_file;
|
|
236 backing_sorce.push_attribute(("file", backing_file));
|
|
237 writer.write_event(Event::Empty(backing_sorce)).unwrap();
|
|
238 writer.write(b"\n").unwrap();
|
|
239
|
|
240 let backing_store_end = BytesEnd::borrowed(b"backingStore");
|
|
241 writer.write_event(Event::End(backing_store_end)).unwrap();
|
|
242 writer.write(b"\n").unwrap();
|
|
243 }
|
20
|
244 }
|
|
245
|
29
|
246 Ok(Event::Text(ref e)) if e.escaped() == IE_VIRSH_TEMPLATE_VM_NAME.as_bytes() => {
|
26
|
247 let elem = BytesText::from_plain_str(&self.vm_name);
|
20
|
248 writer.write_event(Event::Text(elem)).unwrap();
|
|
249 }
|
|
250 Ok(Event::Eof) => break,
|
|
251 // you can use either `e` or `&e` if you don't want to move the event
|
|
252 Ok(e) => assert!(writer.write_event(&e).is_ok()),
|
|
253 Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
|
19
|
254 }
|
20
|
255 buf.clear();
|
19
|
256 }
|
26
|
257 println!("generate xml : {}", self.xml_path);
|
|
258 println!("vnc password : {}", self.vnc_password);
|
|
259 Ok(self.xml_path)
|
19
|
260 }
|
|
261 }
|
|
262
|
|
263 fn generate_pw() -> String {
|
|
264 let mut rng = rand::thread_rng();
|
|
265
|
|
266 (0..PASSWORD_LEN)
|
|
267 .map(|_| {
|
|
268 let idx = rng.gen_range(0, CHARSET.len());
|
|
269 CHARSET[idx] as char
|
|
270 })
|
|
271 .collect()
|
|
272 }
|