1use std::collections::VecDeque;
8
9use anyhow::bail;
10use anyhow::Context;
11use anyhow::Result;
12use balloon_control::BalloonTubeCommand;
13use balloon_control::BalloonTubeResult;
14use base::error;
15use base::Error as SysError;
16use base::Tube;
17use serde::Deserialize;
18use serde::Serialize;
19
20use crate::VmResponse;
21
22#[derive(Serialize, Deserialize, Debug, Clone)]
24pub enum BalloonControlCommand {
25 Adjust {
27 num_bytes: u64,
28 wait_for_success: bool,
29 },
30 Stats,
31 WorkingSet,
32 WorkingSetConfig {
33 bins: Vec<u32>,
34 refresh_threshold: u32,
35 report_threshold: u32,
36 },
37}
38
39fn do_send(tube: &Tube, cmd: &BalloonControlCommand) -> Option<VmResponse> {
40 match *cmd {
41 BalloonControlCommand::Adjust {
42 num_bytes,
43 wait_for_success,
44 } => {
45 match tube.send(&BalloonTubeCommand::Adjust {
46 num_bytes,
47 allow_failure: wait_for_success,
48 }) {
49 Ok(_) => {
50 if wait_for_success {
51 None
52 } else {
53 Some(VmResponse::Ok)
54 }
55 }
56 Err(_) => Some(VmResponse::Err(SysError::last())),
57 }
58 }
59 BalloonControlCommand::WorkingSetConfig {
60 ref bins,
61 refresh_threshold,
62 report_threshold,
63 } => {
64 match tube.send(&BalloonTubeCommand::WorkingSetConfig {
65 bins: bins.clone(),
66 refresh_threshold,
67 report_threshold,
68 }) {
69 Ok(_) => Some(VmResponse::Ok),
70 Err(_) => Some(VmResponse::Err(SysError::last())),
71 }
72 }
73 BalloonControlCommand::Stats => match tube.send(&BalloonTubeCommand::Stats) {
74 Ok(_) => None,
75 Err(_) => Some(VmResponse::Err(SysError::last())),
76 },
77 BalloonControlCommand::WorkingSet => match tube.send(&BalloonTubeCommand::WorkingSet) {
78 Ok(_) => None,
79 Err(_) => Some(VmResponse::Err(SysError::last())),
80 },
81 }
82}
83
84pub struct BalloonTube {
87 tube: Tube,
88 pending_queue: VecDeque<(BalloonControlCommand, Option<usize>)>,
89 pending_adjust_with_completion: Option<(u64, usize)>,
90 activated: bool,
91}
92
93impl BalloonTube {
94 pub fn new(tube: Tube) -> Self {
95 BalloonTube {
96 tube,
97 pending_queue: VecDeque::new(),
98 pending_adjust_with_completion: None,
99 activated: false,
100 }
101 }
102
103 pub fn send_cmd(
106 &mut self,
107 cmd: BalloonControlCommand,
108 key: Option<usize>,
109 ) -> Option<(VmResponse, usize)> {
110 if !self.activated {
111 return key.map(|k| (VmResponse::Err(base::Error::new(libc::ENOTCONN)), k));
112 }
113 match cmd {
114 BalloonControlCommand::Adjust {
115 wait_for_success: true,
116 num_bytes,
117 } => {
118 let Some(key) = key else {
119 error!("Asked for completion without reply key");
120 return None;
121 };
122 let resp = self
123 .pending_adjust_with_completion
124 .take()
125 .map(|(_, key)| (VmResponse::ErrString("Adjust overriden".to_string()), key));
126 if do_send(&self.tube, &cmd).is_some() {
127 unreachable!("Unexpected early reply");
128 }
129 self.pending_adjust_with_completion = Some((num_bytes, key));
130 resp
131 }
132 _ => {
133 if !self.pending_queue.is_empty() {
134 self.pending_queue.push_back((cmd, key));
135 return None;
136 }
137 let resp = do_send(&self.tube, &cmd);
138 if resp.is_none() {
139 self.pending_queue.push_back((cmd, key));
140 }
141 match key {
142 None => None,
143 Some(key) => resp.map(|r| (r, key)),
144 }
145 }
146 }
147 }
148
149 pub fn recv(&mut self) -> Result<Vec<(VmResponse, usize)>> {
152 let res = self
153 .tube
154 .recv::<BalloonTubeResult>()
155 .context("failed to read balloon tube")?;
156 match &res {
157 BalloonTubeResult::Ready => {
158 self.activated = true;
159 return Ok(vec![]);
160 }
161 BalloonTubeResult::NotReady => {
162 self.activated = false;
163 return Ok(vec![]);
164 }
165 _ => {}
166 }
167 if let BalloonTubeResult::Adjusted { num_bytes: actual } = res {
168 let Some((target, key)) = self.pending_adjust_with_completion else {
169 bail!("Unexpected balloon adjust to {}", actual);
170 };
171 if actual != target {
172 return Ok(vec![]);
173 }
174 self.pending_adjust_with_completion.take();
175 return Ok(vec![(VmResponse::Ok, key)]);
176 }
177 let mut responses = vec![];
178 if self.pending_queue.is_empty() {
179 bail!("Unexpected balloon tube result {:?}", res)
180 }
181 let resp = match (
182 &self.pending_queue.front().expect("entry disappeared").0,
183 res,
184 ) {
185 (
186 BalloonControlCommand::Stats,
187 BalloonTubeResult::Stats {
188 stats,
189 balloon_actual,
190 },
191 ) => VmResponse::BalloonStats {
192 stats,
193 balloon_actual,
194 },
195 (
196 BalloonControlCommand::WorkingSet,
197 BalloonTubeResult::WorkingSet { ws, balloon_actual },
198 ) => VmResponse::BalloonWS { ws, balloon_actual },
199 (_, resp) => {
200 bail!("Unexpected balloon tube result {:?}", resp);
201 }
202 };
203 let key = self.pending_queue.pop_front().expect("entry disappeared").1;
204 if let Some(key) = key {
205 responses.push((resp, key))
206 }
207 while let Some((cmd, key)) = self.pending_queue.front() {
208 match do_send(&self.tube, cmd) {
209 Some(resp) => {
210 if let Some(key) = key {
211 responses.push((resp, *key));
212 }
213 self.pending_queue.pop_front();
214 }
215 None => break,
216 }
217 }
218 Ok(responses)
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use balloon_control::BalloonStats;
225
226 use super::*;
227
228 fn activate_tube(balloon_tube: &mut BalloonTube, device: &Tube) {
229 device.send(&BalloonTubeResult::Ready).unwrap();
230 let resp = balloon_tube.recv().unwrap();
231 assert!(resp.is_empty());
232 }
233
234 fn balloon_device_respond_stats(device: &Tube) {
235 let BalloonTubeCommand::Stats = device.recv::<BalloonTubeCommand>().unwrap() else {
236 panic!("unexpected command");
237 };
238
239 device
240 .send(&BalloonTubeResult::Stats {
241 stats: BalloonStats::default(),
242 balloon_actual: 0,
243 })
244 .unwrap();
245 }
246
247 #[test]
248 fn test_send_cmd_not_ready() {
249 let (host, _device) = Tube::pair().unwrap();
250 let mut balloon_tube = BalloonTube::new(host);
251
252 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
254 assert!(resp.is_some());
255 let (response, key) = resp.unwrap();
256 assert_eq!(key, 0xc0ffee);
257 if let VmResponse::Err(err) = response {
258 assert_eq!(err.errno(), libc::ENOTCONN);
259 } else {
260 panic!("unexpected response {response:?}");
261 }
262 }
263
264 #[test]
265 fn test_stat_command() {
266 let (host, device) = Tube::pair().unwrap();
267 let mut balloon_tube = BalloonTube::new(host);
268 activate_tube(&mut balloon_tube, &device);
269
270 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
271 assert!(resp.is_none());
272
273 balloon_device_respond_stats(&device);
274
275 let resp = balloon_tube.recv().unwrap();
276 assert_eq!(resp.len(), 1);
277 assert_eq!(resp[0].1, 0xc0ffee);
278 assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
279 }
280
281 #[test]
282 fn test_multiple_stat_command() {
283 let (host, device) = Tube::pair().unwrap();
284 let mut balloon_tube = BalloonTube::new(host);
285 activate_tube(&mut balloon_tube, &device);
286
287 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
288 assert!(resp.is_none());
289 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xbadcafe));
290 assert!(resp.is_none());
291
292 balloon_device_respond_stats(&device);
293
294 let resp = balloon_tube.recv().unwrap();
295 assert_eq!(resp.len(), 1);
296 assert_eq!(resp[0].1, 0xc0ffee);
297 assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
298
299 balloon_device_respond_stats(&device);
300
301 let resp = balloon_tube.recv().unwrap();
302 assert_eq!(resp.len(), 1);
303 assert_eq!(resp[0].1, 0xbadcafe);
304 assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
305 }
306
307 #[test]
308 fn test_queued_stats_adjust_no_reply() {
309 let (host, device) = Tube::pair().unwrap();
310 let mut balloon_tube = BalloonTube::new(host);
311 activate_tube(&mut balloon_tube, &device);
312
313 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
314 assert!(resp.is_none());
315 let resp = balloon_tube.send_cmd(
316 BalloonControlCommand::Adjust {
317 num_bytes: 0,
318 wait_for_success: false,
319 },
320 Some(0xbadcafe),
321 );
322 assert!(resp.is_none());
323
324 balloon_device_respond_stats(&device);
325
326 let resp = balloon_tube.recv().unwrap();
327 assert_eq!(resp.len(), 2);
328 assert_eq!(resp[0].1, 0xc0ffee);
329 assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
330 assert_eq!(resp[1].1, 0xbadcafe);
331 assert!(matches!(resp[1].0, VmResponse::Ok));
332
333 let cmd = device.recv::<BalloonTubeCommand>().unwrap();
334 assert!(matches!(cmd, BalloonTubeCommand::Adjust { .. }));
335 }
336
337 #[test]
338 fn test_adjust_with_reply() {
339 let (host, device) = Tube::pair().unwrap();
340 let mut balloon_tube = BalloonTube::new(host);
341 activate_tube(&mut balloon_tube, &device);
342
343 let resp = balloon_tube.send_cmd(
344 BalloonControlCommand::Adjust {
345 num_bytes: 0xc0ffee,
346 wait_for_success: true,
347 },
348 Some(0xc0ffee),
349 );
350 assert!(resp.is_none());
351 let cmd = device.recv::<BalloonTubeCommand>().unwrap();
352 assert!(matches!(cmd, BalloonTubeCommand::Adjust { .. }));
353
354 let resp = balloon_tube.send_cmd(
355 BalloonControlCommand::Adjust {
356 num_bytes: 0xbadcafe,
357 wait_for_success: true,
358 },
359 Some(0xbadcafe),
360 );
361 assert!(matches!(resp, Some((VmResponse::ErrString(_), 0xc0ffee))));
362 let cmd = device.recv::<BalloonTubeCommand>().unwrap();
363 assert!(matches!(cmd, BalloonTubeCommand::Adjust { .. }));
364
365 device
366 .send(&BalloonTubeResult::Adjusted {
367 num_bytes: 0xc0ffee,
368 })
369 .unwrap();
370 let resp = balloon_tube.recv().unwrap();
371 assert_eq!(resp.len(), 0);
372
373 device
374 .send(&BalloonTubeResult::Adjusted {
375 num_bytes: 0xbadcafe,
376 })
377 .unwrap();
378 let resp = balloon_tube.recv().unwrap();
379 assert_eq!(resp.len(), 1);
380 assert_eq!(resp[0].1, 0xbadcafe);
381 assert!(matches!(resp[0].0, VmResponse::Ok));
382 }
383
384 #[test]
385 fn test_stats_and_adjust_with_reply() {
386 let (host, device) = Tube::pair().unwrap();
387 let mut balloon_tube = BalloonTube::new(host);
388 activate_tube(&mut balloon_tube, &device);
389
390 let resp = balloon_tube.send_cmd(BalloonControlCommand::Stats, Some(0xc0ffee));
391 assert!(resp.is_none());
392
393 let resp = balloon_tube.send_cmd(
394 BalloonControlCommand::Adjust {
395 num_bytes: 0xbadcafe,
396 wait_for_success: true,
397 },
398 Some(0xbadcafe),
399 );
400 assert!(resp.is_none());
401
402 let cmd = device.recv::<BalloonTubeCommand>().unwrap();
403 assert!(matches!(cmd, BalloonTubeCommand::Stats));
404 let cmd = device.recv::<BalloonTubeCommand>().unwrap();
405 assert!(matches!(cmd, BalloonTubeCommand::Adjust { .. }));
406
407 device
408 .send(&BalloonTubeResult::Adjusted {
409 num_bytes: 0xbadcafe,
410 })
411 .unwrap();
412 let resp = balloon_tube.recv().unwrap();
413 assert_eq!(resp.len(), 1);
414 assert_eq!(resp[0].1, 0xbadcafe);
415 assert!(matches!(resp[0].0, VmResponse::Ok));
416
417 device
418 .send(&BalloonTubeResult::Stats {
419 stats: BalloonStats::default(),
420 balloon_actual: 0,
421 })
422 .unwrap();
423 let resp = balloon_tube.recv().unwrap();
424 assert_eq!(resp.len(), 1);
425 assert_eq!(resp[0].1, 0xc0ffee);
426 assert!(matches!(resp[0].0, VmResponse::BalloonStats { .. }));
427 }
428}