diff --git a/src/dragonball/src/config_manager.rs b/src/dragonball/src/config_manager.rs new file mode 100644 index 0000000000..de16ab0fbf --- /dev/null +++ b/src/dragonball/src/config_manager.rs @@ -0,0 +1,724 @@ +// Copyright (C) 2020-2022 Alibaba Cloud. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +use std::convert::TryInto; +use std::io; +use std::ops::{Index, IndexMut}; +use std::sync::Arc; + +use dbs_device::DeviceIo; +use dbs_utils::rate_limiter::{RateLimiter, TokenBucket}; +use serde_derive::{Deserialize, Serialize}; + +/// Trait for generic configuration information. +pub trait ConfigItem { + /// Related errors. + type Err; + + /// Get the unique identifier of the configuration item. + fn id(&self) -> &str; + + /// Check whether current configuration item conflicts with another one. + fn check_conflicts(&self, other: &Self) -> std::result::Result<(), Self::Err>; +} + +/// Struct to manage a group of configuration items. +#[derive(Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct ConfigInfos +where + T: ConfigItem + Clone, +{ + configs: Vec, +} + +impl ConfigInfos +where + T: ConfigItem + Clone + Default, +{ + /// Constructor + pub fn new() -> Self { + ConfigInfos::default() + } + + /// Insert a configuration item in the group. + pub fn insert(&mut self, config: T) -> std::result::Result<(), T::Err> { + for item in self.configs.iter() { + config.check_conflicts(item)?; + } + self.configs.push(config); + + Ok(()) + } + + /// Update a configuration item in the group. + pub fn update(&mut self, config: T, err: T::Err) -> std::result::Result<(), T::Err> { + match self.get_index_by_id(&config) { + None => Err(err), + Some(index) => { + for (idx, item) in self.configs.iter().enumerate() { + if idx != index { + config.check_conflicts(item)?; + } + } + self.configs[index] = config; + Ok(()) + } + } + } + + /// Insert or update a configuration item in the group. + pub fn insert_or_update(&mut self, config: T) -> std::result::Result<(), T::Err> { + match self.get_index_by_id(&config) { + None => { + for item in self.configs.iter() { + config.check_conflicts(item)?; + } + + self.configs.push(config) + } + Some(index) => { + for (idx, item) in self.configs.iter().enumerate() { + if idx != index { + config.check_conflicts(item)?; + } + } + self.configs[index] = config; + } + } + + Ok(()) + } + + /// Remove the matching configuration entry. + pub fn remove(&mut self, config: &T) -> Option { + if let Some(index) = self.get_index_by_id(config) { + Some(self.configs.remove(index)) + } else { + None + } + } + + /// Returns an immutable iterator over the config items + pub fn iter(&self) -> ::std::slice::Iter { + self.configs.iter() + } + + /// Get the configuration entry with matching ID. + pub fn get_by_id(&self, item: &T) -> Option<&T> { + let id = item.id(); + + self.configs.iter().rfind(|cfg| cfg.id() == id) + } + + fn get_index_by_id(&self, item: &T) -> Option { + let id = item.id(); + self.configs.iter().position(|cfg| cfg.id() == id) + } +} + +impl Clone for ConfigInfos +where + T: ConfigItem + Clone, +{ + fn clone(&self) -> Self { + ConfigInfos { + configs: self.configs.clone(), + } + } +} + +/// Struct to maintain configuration information for a device. +pub struct DeviceConfigInfo +where + T: ConfigItem + Clone, +{ + /// Configuration information for the device object. + pub config: T, + /// The associated device object. + pub device: Option>, +} + +impl DeviceConfigInfo +where + T: ConfigItem + Clone, +{ + /// Create a new instance of ['DeviceInfoGroup']. + pub fn new(config: T) -> Self { + DeviceConfigInfo { + config, + device: None, + } + } + + /// Create a new instance of ['DeviceInfoGroup'] with optional device. + pub fn new_with_device(config: T, device: Option>) -> Self { + DeviceConfigInfo { config, device } + } + + /// Set the device object associated with the configuration. + pub fn set_device(&mut self, device: Arc) { + self.device = Some(device); + } +} + +impl Clone for DeviceConfigInfo +where + T: ConfigItem + Clone, +{ + fn clone(&self) -> Self { + DeviceConfigInfo::new_with_device(self.config.clone(), self.device.clone()) + } +} + +/// Struct to maintain configuration information for a group of devices. +pub struct DeviceConfigInfos +where + T: ConfigItem + Clone, +{ + info_list: Vec>, +} + +impl DeviceConfigInfos +where + T: ConfigItem + Clone, +{ + /// Create a new instance of ['DeviceConfigInfos']. + pub fn new() -> Self { + DeviceConfigInfos { + info_list: Vec::new(), + } + } + + /// Insert or update configuration information for a device. + pub fn insert_or_update(&mut self, config: &T) -> std::result::Result { + let device_info = DeviceConfigInfo::new(config.clone()); + Ok(match self.get_index_by_id(config) { + Some(index) => { + for (idx, info) in self.info_list.iter().enumerate() { + if idx != index { + info.config.check_conflicts(config)?; + } + } + self.info_list[index] = device_info; + index + } + None => { + for info in self.info_list.iter() { + info.config.check_conflicts(config)?; + } + self.info_list.push(device_info); + self.info_list.len() - 1 + } + }) + } + + /// Remove a device configuration information object. + pub fn remove(&mut self, index: usize) -> Option> { + if self.info_list.len() > index { + Some(self.info_list.remove(index)) + } else { + None + } + } + + #[allow(dead_code)] + /// Get number of device configuration information objects. + pub fn len(&self) -> usize { + self.info_list.len() + } + + /// Add a device configuration information object at the tail. + pub fn push(&mut self, info: DeviceConfigInfo) { + self.info_list.push(info); + } + + /// Iterator for configuration information objects. + pub fn iter(&self) -> std::slice::Iter> { + self.info_list.iter() + } + + /// Mutable iterator for configuration information objects. + pub fn iter_mut(&mut self) -> std::slice::IterMut> { + self.info_list.iter_mut() + } + + fn get_index_by_id(&self, config: &T) -> Option { + self.info_list + .iter() + .position(|info| info.config.id().eq(config.id())) + } +} + +impl Index for DeviceConfigInfos +where + T: ConfigItem + Clone, +{ + type Output = DeviceConfigInfo; + fn index(&self, idx: usize) -> &Self::Output { + &self.info_list[idx] + } +} + +impl IndexMut for DeviceConfigInfos +where + T: ConfigItem + Clone, +{ + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + &mut self.info_list[idx] + } +} + +impl Clone for DeviceConfigInfos +where + T: ConfigItem + Clone, +{ + fn clone(&self) -> Self { + DeviceConfigInfos { + info_list: self.info_list.clone(), + } + } +} + +/// Configuration information for RateLimiter token bucket. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct TokenBucketConfigInfo { + /// The size for the token bucket. A TokenBucket of `size` total capacity will take `refill_time` + /// milliseconds to go from zero tokens to total capacity. + pub size: u64, + /// Number of free initial tokens, that can be consumed at no cost. + pub one_time_burst: u64, + /// Complete refill time in milliseconds. + pub refill_time: u64, +} + +impl TokenBucketConfigInfo { + fn resize(&mut self, n: u64) { + if n != 0 { + self.size /= n; + self.one_time_burst /= n; + } + } +} + +impl From for TokenBucket { + fn from(t: TokenBucketConfigInfo) -> TokenBucket { + (&t).into() + } +} + +impl From<&TokenBucketConfigInfo> for TokenBucket { + fn from(t: &TokenBucketConfigInfo) -> TokenBucket { + TokenBucket::new(t.size, t.one_time_burst, t.refill_time) + } +} + +/// Configuration information for RateLimiter objects. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct RateLimiterConfigInfo { + /// Data used to initialize the RateLimiter::bandwidth bucket. + pub bandwidth: TokenBucketConfigInfo, + /// Data used to initialize the RateLimiter::ops bucket. + pub ops: TokenBucketConfigInfo, +} + +impl RateLimiterConfigInfo { + /// Update the bandwidth budget configuration. + pub fn update_bandwidth(&mut self, new_config: TokenBucketConfigInfo) { + self.bandwidth = new_config; + } + + /// Update the ops budget configuration. + pub fn update_ops(&mut self, new_config: TokenBucketConfigInfo) { + self.ops = new_config; + } + + /// resize the limiter to its 1/n. + pub fn resize(&mut self, n: u64) { + self.bandwidth.resize(n); + self.ops.resize(n); + } +} + +impl TryInto for &RateLimiterConfigInfo { + type Error = io::Error; + + fn try_into(self) -> Result { + RateLimiter::new( + self.bandwidth.size, + self.bandwidth.one_time_burst, + self.bandwidth.refill_time, + self.ops.size, + self.ops.one_time_burst, + self.ops.refill_time, + ) + } +} + +impl TryInto for RateLimiterConfigInfo { + type Error = io::Error; + + fn try_into(self) -> Result { + RateLimiter::new( + self.bandwidth.size, + self.bandwidth.one_time_burst, + self.bandwidth.refill_time, + self.ops.size, + self.ops.one_time_burst, + self.ops.refill_time, + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Debug, thiserror::Error)] + pub enum DummyError { + #[error("configuration entry exists")] + Exist, + } + + #[derive(Clone, Debug)] + pub struct DummyConfigInfo { + id: String, + content: String, + } + + impl ConfigItem for DummyConfigInfo { + type Err = DummyError; + + fn id(&self) -> &str { + &self.id + } + + fn check_conflicts(&self, other: &Self) -> Result<(), DummyError> { + if self.id == other.id || self.content == other.content { + Err(DummyError::Exist) + } else { + Ok(()) + } + } + } + + type DummyConfigInfos = ConfigInfos; + + #[test] + fn test_insert_config_info() { + let mut configs = DummyConfigInfos::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert(config1).unwrap(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + + // Test case: cannot insert new item with the same id. + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.insert(config2).unwrap_err(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert(config3).unwrap(); + assert_eq!(configs.configs.len(), 2); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + assert_eq!(configs.configs[1].id, "2"); + assert_eq!(configs.configs[1].content, "c"); + + // Test case: cannot insert new item with the same content. + let config4 = DummyConfigInfo { + id: "3".to_owned(), + content: "c".to_owned(), + }; + configs.insert(config4).unwrap_err(); + assert_eq!(configs.configs.len(), 2); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + assert_eq!(configs.configs[1].id, "2"); + assert_eq!(configs.configs[1].content, "c"); + } + + #[test] + fn test_update_config_info() { + let mut configs = DummyConfigInfos::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert(config1).unwrap(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + + // Test case: succeed to update an existing entry + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.update(config2, DummyError::Exist).unwrap(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + + // Test case: cannot update a non-existing entry + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.update(config3, DummyError::Exist).unwrap_err(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + + // Test case: cannot update an entry with conflicting content + let config4 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert(config4).unwrap(); + let config5 = DummyConfigInfo { + id: "1".to_owned(), + content: "c".to_owned(), + }; + configs.update(config5, DummyError::Exist).unwrap_err(); + } + + #[test] + fn test_insert_or_update_config_info() { + let mut configs = DummyConfigInfos::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert_or_update(config1).unwrap(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "a"); + + // Test case: succeed to update an existing entry + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.insert_or_update(config2.clone()).unwrap(); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + + // Add a second entry + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(config3.clone()).unwrap(); + assert_eq!(configs.configs.len(), 2); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + assert_eq!(configs.configs[1].id, "2"); + assert_eq!(configs.configs[1].content, "c"); + + // Lookup the first entry + let config4 = configs + .get_by_id(&DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }) + .unwrap(); + assert_eq!(config4.id, config2.id); + assert_eq!(config4.content, config2.content); + + // Lookup the second entry + let config5 = configs + .get_by_id(&DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }) + .unwrap(); + assert_eq!(config5.id, config3.id); + assert_eq!(config5.content, config3.content); + + // Test case: can't insert an entry with conflicting content + let config6 = DummyConfigInfo { + id: "3".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(config6).unwrap_err(); + assert_eq!(configs.configs.len(), 2); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + assert_eq!(configs.configs[1].id, "2"); + assert_eq!(configs.configs[1].content, "c"); + } + + #[test] + fn test_remove_config_info() { + let mut configs = DummyConfigInfos::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert_or_update(config1).unwrap(); + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.insert_or_update(config2.clone()).unwrap(); + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(config3.clone()).unwrap(); + assert_eq!(configs.configs.len(), 2); + assert_eq!(configs.configs[0].id, "1"); + assert_eq!(configs.configs[0].content, "b"); + assert_eq!(configs.configs[1].id, "2"); + assert_eq!(configs.configs[1].content, "c"); + + let config4 = configs + .remove(&DummyConfigInfo { + id: "1".to_owned(), + content: "no value".to_owned(), + }) + .unwrap(); + assert_eq!(config4.id, config2.id); + assert_eq!(config4.content, config2.content); + assert_eq!(configs.configs.len(), 1); + assert_eq!(configs.configs[0].id, "2"); + assert_eq!(configs.configs[0].content, "c"); + + let config5 = configs + .remove(&DummyConfigInfo { + id: "2".to_owned(), + content: "no value".to_owned(), + }) + .unwrap(); + assert_eq!(config5.id, config3.id); + assert_eq!(config5.content, config3.content); + assert_eq!(configs.configs.len(), 0); + } + + type DummyDeviceInfoList = DeviceConfigInfos; + + #[test] + fn test_insert_or_update_device_info() { + let mut configs = DummyDeviceInfoList::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert_or_update(&config1).unwrap(); + assert_eq!(configs.len(), 1); + assert_eq!(configs[0].config.id, "1"); + assert_eq!(configs[0].config.content, "a"); + + // Test case: succeed to update an existing entry + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.insert_or_update(&config2 /* */).unwrap(); + assert_eq!(configs.len(), 1); + assert_eq!(configs[0].config.id, "1"); + assert_eq!(configs[0].config.content, "b"); + + // Add a second entry + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(&config3).unwrap(); + assert_eq!(configs.len(), 2); + assert_eq!(configs[0].config.id, "1"); + assert_eq!(configs[0].config.content, "b"); + assert_eq!(configs[1].config.id, "2"); + assert_eq!(configs[1].config.content, "c"); + + // Lookup the first entry + let config4_id = configs + .get_index_by_id(&DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }) + .unwrap(); + let config4 = &configs[config4_id].config; + assert_eq!(config4.id, config2.id); + assert_eq!(config4.content, config2.content); + + // Lookup the second entry + let config5_id = configs + .get_index_by_id(&DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }) + .unwrap(); + let config5 = &configs[config5_id].config; + assert_eq!(config5.id, config3.id); + assert_eq!(config5.content, config3.content); + + // Test case: can't insert an entry with conflicting content + let config6 = DummyConfigInfo { + id: "3".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(&config6).unwrap_err(); + assert_eq!(configs.len(), 2); + assert_eq!(configs[0].config.id, "1"); + assert_eq!(configs[0].config.content, "b"); + assert_eq!(configs[1].config.id, "2"); + assert_eq!(configs[1].config.content, "c"); + } + + #[test] + fn test_remove_device_info() { + let mut configs = DummyDeviceInfoList::new(); + + let config1 = DummyConfigInfo { + id: "1".to_owned(), + content: "a".to_owned(), + }; + configs.insert_or_update(&config1).unwrap(); + let config2 = DummyConfigInfo { + id: "1".to_owned(), + content: "b".to_owned(), + }; + configs.insert_or_update(&config2).unwrap(); + let config3 = DummyConfigInfo { + id: "2".to_owned(), + content: "c".to_owned(), + }; + configs.insert_or_update(&config3).unwrap(); + assert_eq!(configs.len(), 2); + assert_eq!(configs[0].config.id, "1"); + assert_eq!(configs[0].config.content, "b"); + assert_eq!(configs[1].config.id, "2"); + assert_eq!(configs[1].config.content, "c"); + + let config4 = configs.remove(0).unwrap().config; + assert_eq!(config4.id, config2.id); + assert_eq!(config4.content, config2.content); + assert_eq!(configs.len(), 1); + assert_eq!(configs[0].config.id, "2"); + assert_eq!(configs[0].config.content, "c"); + + let config5 = configs.remove(0).unwrap().config; + assert_eq!(config5.id, config3.id); + assert_eq!(config5.content, config3.content); + assert_eq!(configs.len(), 0); + } +} diff --git a/src/dragonball/src/lib.rs b/src/dragonball/src/lib.rs index a3295e8f8f..8bff8b0436 100644 --- a/src/dragonball/src/lib.rs +++ b/src/dragonball/src/lib.rs @@ -8,6 +8,8 @@ /// Address space manager for virtual machines. pub mod address_space_manager; +/// Structs to maintain configuration information. +pub mod config_manager; /// Device manager for virtual machines. pub mod device_manager; /// Resource manager for virtual machines.