vmm/vmm_config/
balloon.rs

1// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::sync::{Arc, Mutex};
5
6use serde::{Deserialize, Serialize};
7
8pub use crate::devices::virtio::balloon::BALLOON_DEV_ID;
9pub use crate::devices::virtio::balloon::device::BalloonStats;
10use crate::devices::virtio::balloon::{Balloon, BalloonConfig};
11
12type MutexBalloon = Arc<Mutex<Balloon>>;
13
14/// Errors associated with the operations allowed on the balloon.
15#[derive(Debug, derive_more::From, thiserror::Error, displaydoc::Display)]
16pub enum BalloonConfigError {
17    /// No balloon device found.
18    DeviceNotFound,
19    /// Amount of pages requested is too large.
20    TooManyPagesRequested,
21    /// Error creating the balloon device: {0}
22    CreateFailure(crate::devices::virtio::balloon::BalloonError),
23}
24
25/// This struct represents the strongly typed equivalent of the json body
26/// from balloon related requests.
27#[derive(Clone, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
28#[serde(deny_unknown_fields)]
29pub struct BalloonDeviceConfig {
30    /// Target balloon size in MiB.
31    pub amount_mib: u32,
32    /// Option to deflate the balloon in case the guest is out of memory.
33    pub deflate_on_oom: bool,
34    /// Interval in seconds between refreshing statistics.
35    #[serde(default)]
36    pub stats_polling_interval_s: u16,
37    /// Free page hinting enabled
38    #[serde(default)]
39    pub free_page_hinting: bool,
40    /// Free page reporting enabled
41    #[serde(default)]
42    pub free_page_reporting: bool,
43}
44
45impl From<BalloonConfig> for BalloonDeviceConfig {
46    fn from(state: BalloonConfig) -> Self {
47        BalloonDeviceConfig {
48            amount_mib: state.amount_mib,
49            deflate_on_oom: state.deflate_on_oom,
50            stats_polling_interval_s: state.stats_polling_interval_s,
51            free_page_hinting: state.free_page_hinting,
52            free_page_reporting: state.free_page_reporting,
53        }
54    }
55}
56
57/// The data fed into a balloon update request. Currently, only the number
58/// of pages and the stats polling interval can be updated.
59#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
60#[serde(deny_unknown_fields)]
61pub struct BalloonUpdateConfig {
62    /// Target balloon size in MiB.
63    pub amount_mib: u32,
64}
65
66/// The data fed into a balloon statistics interval update request.
67/// Note that the state of the statistics cannot be changed from ON to OFF
68/// or vice versa after boot, only the interval of polling can be changed
69/// if the statistics were activated in the device configuration.
70#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
71#[serde(deny_unknown_fields)]
72pub struct BalloonUpdateStatsConfig {
73    /// Interval in seconds between refreshing statistics.
74    pub stats_polling_interval_s: u16,
75}
76
77/// A builder for `Balloon` devices from 'BalloonDeviceConfig'.
78#[cfg_attr(not(test), derive(Default))]
79#[derive(Debug)]
80pub struct BalloonBuilder {
81    inner: Option<MutexBalloon>,
82}
83
84impl BalloonBuilder {
85    /// Creates an empty Balloon Store.
86    pub fn new() -> Self {
87        Self { inner: None }
88    }
89
90    /// Inserts a Balloon device in the store.
91    /// If an entry already exists, it will overwrite it.
92    pub fn set(&mut self, cfg: BalloonDeviceConfig) -> Result<(), BalloonConfigError> {
93        self.inner = Some(Arc::new(Mutex::new(Balloon::new(
94            cfg.amount_mib,
95            cfg.deflate_on_oom,
96            cfg.stats_polling_interval_s,
97            cfg.free_page_hinting,
98            cfg.free_page_reporting,
99        )?)));
100
101        Ok(())
102    }
103
104    /// Inserts an existing balloon device.
105    pub fn set_device(&mut self, balloon: MutexBalloon) {
106        self.inner = Some(balloon);
107    }
108
109    /// Provides a reference to the Balloon if present.
110    pub fn get(&self) -> Option<&MutexBalloon> {
111        self.inner.as_ref()
112    }
113
114    /// Returns the same structure that was used to configure the device.
115    pub fn get_config(&self) -> Result<BalloonDeviceConfig, BalloonConfigError> {
116        self.get()
117            .ok_or(BalloonConfigError::DeviceNotFound)
118            .map(|balloon_mutex| balloon_mutex.lock().expect("Poisoned lock").config())
119            .map(BalloonDeviceConfig::from)
120    }
121}
122
123#[cfg(test)]
124impl Default for BalloonBuilder {
125    fn default() -> BalloonBuilder {
126        let mut balloon = BalloonBuilder::new();
127        balloon.set(BalloonDeviceConfig::default()).unwrap();
128        balloon
129    }
130}
131
132#[cfg(test)]
133pub(crate) mod tests {
134    use super::*;
135
136    pub(crate) fn default_config() -> BalloonDeviceConfig {
137        BalloonDeviceConfig {
138            amount_mib: 0,
139            deflate_on_oom: false,
140            stats_polling_interval_s: 0,
141            free_page_hinting: false,
142            free_page_reporting: false,
143        }
144    }
145
146    #[test]
147    fn test_balloon_create() {
148        let default_balloon_config = default_config();
149        let balloon_config = BalloonDeviceConfig {
150            amount_mib: 0,
151            deflate_on_oom: false,
152            stats_polling_interval_s: 0,
153            free_page_hinting: false,
154            free_page_reporting: false,
155        };
156        assert_eq!(default_balloon_config, balloon_config);
157        let mut builder = BalloonBuilder::new();
158        assert!(builder.get().is_none());
159
160        builder.set(balloon_config).unwrap();
161        assert_eq!(builder.get().unwrap().lock().unwrap().num_pages(), 0);
162        assert_eq!(builder.get_config().unwrap(), default_balloon_config);
163
164        let _update_config = BalloonUpdateConfig { amount_mib: 5 };
165        let _stats_update_config = BalloonUpdateStatsConfig {
166            stats_polling_interval_s: 5,
167        };
168    }
169
170    #[test]
171    fn test_from_balloon_state() {
172        let expected_balloon_config = BalloonDeviceConfig {
173            amount_mib: 5,
174            deflate_on_oom: false,
175            stats_polling_interval_s: 3,
176            free_page_hinting: false,
177            free_page_reporting: false,
178        };
179
180        let actual_balloon_config = BalloonDeviceConfig::from(BalloonConfig {
181            amount_mib: 5,
182            deflate_on_oom: false,
183            stats_polling_interval_s: 3,
184            free_page_hinting: false,
185            free_page_reporting: false,
186        });
187
188        assert_eq!(expected_balloon_config, actual_balloon_config);
189    }
190
191    #[test]
192    fn test_set_device() {
193        let mut builder = BalloonBuilder::new();
194        let balloon = Balloon::new(0, true, 0, false, false).unwrap();
195        builder.set_device(Arc::new(Mutex::new(balloon)));
196        assert!(builder.inner.is_some());
197    }
198}