use std::collections::BTreeMap;
use crate::error::{CoreError, CoreResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LedgerType {
Root,
Domain,
Queue,
Connection,
}
impl LedgerType {
pub fn depth(&self) -> u8 {
match self {
LedgerType::Root => 0,
LedgerType::Domain => 1,
LedgerType::Queue => 2,
LedgerType::Connection => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ResourceType {
Memory,
Frames,
Connections,
}
impl ResourceType {
pub fn name(&self) -> &'static str {
match self {
ResourceType::Memory => "memory",
ResourceType::Frames => "frames",
ResourceType::Connections => "connections",
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LedgerQuota {
pub memory_bytes: u64,
pub frame_count: u64,
pub connection_count: u64,
}
impl LedgerQuota {
#[inline]
pub const fn zero() -> Self {
Self {
memory_bytes: 0,
frame_count: 0,
connection_count: 0,
}
}
#[inline]
pub const fn unlimited() -> Self {
Self {
memory_bytes: u64::MAX,
frame_count: u64::MAX,
connection_count: u64::MAX,
}
}
#[inline]
pub fn can_allocate(&self, child: &LedgerQuota) -> CoreResult<()> {
if child.memory_bytes > self.memory_bytes {
return Err(CoreError::quota_exceeded(
"memory",
self.memory_bytes,
child.memory_bytes,
));
}
if child.frame_count > self.frame_count {
return Err(CoreError::quota_exceeded(
"frames",
self.frame_count,
child.frame_count,
));
}
if child.connection_count > self.connection_count {
return Err(CoreError::quota_exceeded(
"connections",
self.connection_count,
child.connection_count,
));
}
Ok(())
}
#[inline]
pub fn consume(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
let field = match resource {
ResourceType::Memory => &mut self.memory_bytes,
ResourceType::Frames => &mut self.frame_count,
ResourceType::Connections => &mut self.connection_count,
};
*field = field
.checked_sub(amount)
.ok_or_else(|| CoreError::arithmetic_overflow("sub", *field, amount))?;
Ok(())
}
#[inline]
pub fn restore(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
let field = match resource {
ResourceType::Memory => &mut self.memory_bytes,
ResourceType::Frames => &mut self.frame_count,
ResourceType::Connections => &mut self.connection_count,
};
*field = field
.checked_add(amount)
.ok_or_else(|| CoreError::arithmetic_overflow("add", *field, amount))?;
Ok(())
}
#[inline]
pub fn get(&self, resource: ResourceType) -> u64 {
match resource {
ResourceType::Memory => self.memory_bytes,
ResourceType::Frames => self.frame_count,
ResourceType::Connections => self.connection_count,
}
}
pub fn remaining(&self, used: &LedgerQuota) -> LedgerQuota {
LedgerQuota {
memory_bytes: self.memory_bytes.saturating_sub(used.memory_bytes),
frame_count: self.frame_count.saturating_sub(used.frame_count),
connection_count: self.connection_count.saturating_sub(used.connection_count),
}
}
}
#[derive(Debug)]
pub struct ResourceLedger {
name: String,
ledger_type: LedgerType,
total: LedgerQuota,
used: LedgerQuota,
used_direct: LedgerQuota,
allocated_to_children: LedgerQuota,
children: BTreeMap<String, ResourceLedger>,
}
impl ResourceLedger {
pub fn new(name: impl Into<String>, ledger_type: LedgerType, total: LedgerQuota) -> Self {
Self {
name: name.into(),
ledger_type,
total,
used: LedgerQuota::zero(),
used_direct: LedgerQuota::zero(),
allocated_to_children: LedgerQuota::zero(),
children: BTreeMap::new(),
}
}
pub fn root(total: LedgerQuota) -> Self {
Self::new("root", LedgerType::Root, total)
}
#[inline]
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub fn ledger_type(&self) -> LedgerType {
self.ledger_type
}
#[inline]
pub fn depth(&self) -> u8 {
self.ledger_type.depth()
}
#[inline]
pub fn total(&self) -> &LedgerQuota {
&self.total
}
#[inline]
pub fn used(&self) -> &LedgerQuota {
&self.used
}
#[inline]
pub fn allocated_to_children(&self) -> &LedgerQuota {
&self.allocated_to_children
}
#[inline]
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn child_names(&self) -> impl Iterator<Item = &str> {
self.children.keys().map(|s| s.as_str())
}
pub fn get_child(&self, name: &str) -> Option<&ResourceLedger> {
self.children.get(name)
}
pub fn get_child_mut(&mut self, name: &str) -> Option<&mut ResourceLedger> {
self.children.get_mut(name)
}
pub fn create_child(
&mut self,
name: impl Into<String>,
ledger_type: LedgerType,
quota: LedgerQuota,
) -> CoreResult<&mut ResourceLedger> {
let name: String = name.into();
if quota.memory_bytes == 0
&& quota.frame_count == 0
&& quota.connection_count == 0
{
return Err(CoreError::invalid_config(
"LedgerQuota",
"all quota fields are zero; a child ledger with zero total quota can never allocate any resource",
));
}
if self.children.contains_key(&name) {
return Err(CoreError::resource_already_exists(
0,
"ledger",
));
}
for resource in [ResourceType::Memory, ResourceType::Frames, ResourceType::Connections] {
let committed = self
.allocated_to_children
.get(resource)
.checked_add(quota.get(resource))
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"add",
self.allocated_to_children.get(resource),
quota.get(resource),
)
})?;
let available = self
.total
.get(resource)
.checked_sub(self.used.get(resource))
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.total.get(resource),
self.used.get(resource),
)
})?;
if committed > available {
return Err(CoreError::quota_exceeded(
resource.name(),
available,
committed,
));
}
}
self.allocated_to_children.memory_bytes = self
.allocated_to_children
.memory_bytes
.checked_add(quota.memory_bytes)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"add",
self.allocated_to_children.memory_bytes,
quota.memory_bytes,
)
})?;
self.allocated_to_children.frame_count = self
.allocated_to_children
.frame_count
.checked_add(quota.frame_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"add",
self.allocated_to_children.frame_count,
quota.frame_count,
)
})?;
self.allocated_to_children.connection_count = self
.allocated_to_children
.connection_count
.checked_add(quota.connection_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"add",
self.allocated_to_children.connection_count,
quota.connection_count,
)
})?;
let child = ResourceLedger::new(name.clone(), ledger_type, quota);
self.children.insert(name.clone(), child);
self.children.get_mut(&name).ok_or_else(|| CoreError::internal("child ledger not found after insertion"))
}
pub fn remove_child(&mut self, name: &str) -> CoreResult<()> {
let child = self
.children
.remove(name)
.ok_or_else(|| CoreError::resource_not_found(0, "ledger"))?;
self.used.memory_bytes = self
.used
.memory_bytes
.checked_sub(child.used.memory_bytes)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.used.memory_bytes,
child.used.memory_bytes,
)
})?;
self.used.frame_count = self
.used
.frame_count
.checked_sub(child.used.frame_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.used.frame_count,
child.used.frame_count,
)
})?;
self.used.connection_count = self
.used
.connection_count
.checked_sub(child.used.connection_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.used.connection_count,
child.used.connection_count,
)
})?;
self.allocated_to_children.memory_bytes = self
.allocated_to_children
.memory_bytes
.checked_sub(child.total.memory_bytes)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.allocated_to_children.memory_bytes,
child.total.memory_bytes,
)
})?;
self.allocated_to_children.frame_count = self
.allocated_to_children
.frame_count
.checked_sub(child.total.frame_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.allocated_to_children.frame_count,
child.total.frame_count,
)
})?;
self.allocated_to_children.connection_count = self
.allocated_to_children
.connection_count
.checked_sub(child.total.connection_count)
.ok_or_else(|| {
CoreError::arithmetic_overflow(
"sub",
self.allocated_to_children.connection_count,
child.total.connection_count,
)
})?;
Ok(())
}
pub fn allocate(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
let direct = self.used_direct.get(resource);
let total = self.total.get(resource);
let new_direct = direct
.checked_add(amount)
.ok_or_else(|| CoreError::arithmetic_overflow("add", direct, amount))?;
let committed = self.allocated_to_children.get(resource);
let total_need = new_direct
.checked_add(committed)
.ok_or_else(|| CoreError::arithmetic_overflow("add", new_direct, committed))?;
if total_need > total {
return Err(CoreError::quota_exceeded(resource.name(), total, total_need));
}
self.used_direct.restore(resource, amount)?;
self.used.restore(resource, amount)?;
Ok(())
}
fn allocate_rollup(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
let current = self.used.get(resource);
let total = self.total.get(resource);
let new_used = current
.checked_add(amount)
.ok_or_else(|| CoreError::arithmetic_overflow("add", current, amount))?;
if new_used > total {
return Err(CoreError::quota_exceeded(resource.name(), total, new_used));
}
self.used.restore(resource, amount)?;
Ok(())
}
pub fn release(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
self.used_direct.consume(resource, amount)?;
self.used.consume(resource, amount)?;
Ok(())
}
fn release_rollup(&mut self, resource: ResourceType, amount: u64) -> CoreResult<()> {
self.used.consume(resource, amount)?;
Ok(())
}
pub fn allocate_in_child(
&mut self,
child_path: &[&str],
resource: ResourceType,
amount: u64,
) -> CoreResult<()> {
match child_path.split_first() {
None => self.allocate(resource, amount),
Some((head, rest)) => {
if !self.children.contains_key(*head) {
return Err(CoreError::resource_not_found(0, "ledger"));
}
self.allocate_rollup(resource, amount)?;
let child = self
.children
.get_mut(*head)
.ok_or_else(|| CoreError::internal("child ledger not found after existence check"))?;
match child.allocate_in_child(rest, resource, amount) {
Ok(()) => Ok(()),
Err(e) => {
self.release_rollup(resource, amount)?;
Err(e)
}
}
}
}
}
pub fn release_in_child(
&mut self,
child_path: &[&str],
resource: ResourceType,
amount: u64,
) -> CoreResult<()> {
match child_path.split_first() {
None => self.release(resource, amount),
Some((head, rest)) => {
if !self.children.contains_key(*head) {
return Err(CoreError::resource_not_found(0, "ledger"));
}
self.release_rollup(resource, amount)?;
let child = self
.children
.get_mut(*head)
.ok_or_else(|| CoreError::internal("child ledger not found after existence check"))?;
match child.release_in_child(rest, resource, amount) {
Ok(()) => Ok(()),
Err(e) => {
self.used.restore(resource, amount)?;
Err(e)
}
}
}
}
}
#[inline]
pub fn can_allocate(&self, resource: ResourceType, amount: u64) -> bool {
let direct = self.used_direct.get(resource);
let total = self.total.get(resource);
let committed = self.allocated_to_children.get(resource);
match direct
.checked_add(amount)
.and_then(|v| v.checked_add(committed))
{
Some(need) => need <= total,
None => false,
}
}
pub fn remaining(&self) -> LedgerQuota {
self.total.remaining(&self.used)
}
pub fn max_depth(&self) -> u8 {
let mut max = self.depth();
for child in self.children.values() {
max = max.max(child.max_depth());
}
max
}
pub fn node_count(&self) -> u64 {
self.children
.values()
.map(ResourceLedger::node_count)
.fold(1u64, u64::saturating_add)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_quota(mem: u64, frames: u64, conns: u64) -> LedgerQuota {
LedgerQuota {
memory_bytes: mem,
frame_count: frames,
connection_count: conns,
}
}
#[test]
fn test_root_ledger_create() {
let root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
assert_eq!(root.name(), "root");
assert_eq!(root.ledger_type(), LedgerType::Root);
assert_eq!(root.depth(), 0);
assert_eq!(root.total().memory_bytes, 1 << 30);
assert_eq!(root.used().memory_bytes, 0);
assert_eq!(root.child_count(), 0);
assert_eq!(root.node_count(), 1);
}
#[test]
fn test_create_domain_child() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
assert_eq!(root.child_count(), 1);
assert!(root.get_child("domain_0").is_some());
let domain = root.get_child("domain_0").unwrap();
assert_eq!(domain.ledger_type(), LedgerType::Domain);
assert_eq!(domain.depth(), 1);
assert_eq!(domain.total().frame_count, 10_000);
assert_eq!(root.node_count(), 2);
}
#[test]
fn test_create_child_quota_exceeded() {
let mut root = ResourceLedger::root(make_quota(1024, 100, 10));
let result = root.create_child("big", LedgerType::Domain, make_quota(2048, 0, 0));
assert!(result.is_err());
assert_eq!(root.child_count(), 0);
assert_eq!(root.total().memory_bytes, 1024);
}
#[test]
fn test_duplicate_child_name() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
let result = root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000));
assert!(result.is_err());
}
#[test]
fn test_allocate_and_release() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(4096, 100, 10));
ledger.allocate(ResourceType::Memory, 1024).unwrap();
assert_eq!(ledger.used().memory_bytes, 1024);
ledger.release(ResourceType::Memory, 512).unwrap();
assert_eq!(ledger.used().memory_bytes, 512);
let remaining = ledger.remaining();
assert_eq!(remaining.memory_bytes, 4096 - 512);
}
#[test]
fn test_allocate_quota_exceeded() {
let mut ledger = ResourceLedger::new("small", LedgerType::Queue, make_quota(1024, 0, 0));
ledger.allocate(ResourceType::Memory, 512).unwrap();
let result = ledger.allocate(ResourceType::Memory, 1024);
assert!(result.is_err());
}
#[test]
fn test_allocate_overflow() {
let mut ledger = ResourceLedger::new("max", LedgerType::Queue, make_quota(u64::MAX, 0, 0));
ledger.allocate(ResourceType::Memory, u64::MAX).unwrap();
let result = ledger.allocate(ResourceType::Memory, 1);
assert!(result.is_err());
}
#[test]
fn test_frames_and_connections() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 1000, 50));
ledger.allocate(ResourceType::Frames, 500).unwrap();
assert_eq!(ledger.used().frame_count, 500);
ledger.allocate(ResourceType::Connections, 10).unwrap();
assert_eq!(ledger.used().connection_count, 10);
ledger.release(ResourceType::Frames, 200).unwrap();
assert_eq!(ledger.used().frame_count, 300);
}
#[test]
fn test_can_allocate() {
let ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1 << 30, 0, 0));
assert!(ledger.can_allocate(ResourceType::Memory, 1024));
assert!(ledger.can_allocate(ResourceType::Memory, 1 << 30));
assert!(!ledger.can_allocate(ResourceType::Memory, (1 << 30) + 1));
}
#[test]
fn test_nested_hierarchy() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 24, 50_000, 5_000))
.unwrap();
root.get_child_mut("domain_0").unwrap()
.create_child("queue_0", LedgerType::Queue, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
root.get_child_mut("domain_0").unwrap()
.get_child_mut("queue_0").unwrap()
.create_child("conn_0", LedgerType::Connection, make_quota(1 << 16, 100, 10))
.unwrap();
let conn = root.get_child("domain_0").unwrap()
.get_child("queue_0").unwrap()
.get_child("conn_0").unwrap();
assert_eq!(conn.depth(), 3);
assert_eq!(conn.ledger_type(), LedgerType::Connection);
assert_eq!(root.max_depth(), 3);
assert_eq!(root.node_count(), 4);
root.get_child_mut("domain_0").unwrap()
.get_child_mut("queue_0").unwrap()
.get_child_mut("conn_0").unwrap()
.allocate(ResourceType::Memory, 1024)
.unwrap();
assert_eq!(
root.get_child("domain_0").unwrap()
.get_child("queue_0").unwrap()
.get_child("conn_0").unwrap()
.used()
.memory_bytes,
1024
);
}
#[test]
fn test_remove_child_reclaims_quota() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
root.get_child_mut("domain_0").unwrap()
.create_child("queue_0", LedgerType::Queue, make_quota(1 << 16, 1000, 100))
.unwrap();
root.get_child_mut("domain_0").unwrap()
.get_child_mut("queue_0").unwrap()
.allocate(ResourceType::Memory, 4096)
.unwrap();
let queue_used = root.get_child("domain_0").unwrap()
.get_child("queue_0").unwrap()
.used().memory_bytes;
assert_eq!(queue_used, 4096);
root.remove_child("domain_0").unwrap();
assert_eq!(root.child_count(), 0);
assert_eq!(root.allocated_to_children().memory_bytes, 0);
}
#[test]
fn test_remove_nonexistent_child() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 0, 0));
let result = root.remove_child("nonexistent");
assert!(result.is_err());
}
#[test]
fn test_list_child_names() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
root.create_child("domain_1", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
let names: Vec<&str> = root.child_names().collect();
assert_eq!(names.len(), 2);
assert!(names.contains(&"domain_0"));
assert!(names.contains(&"domain_1"));
}
#[test]
fn test_allocations_accounted_in_parent() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("domain_0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000))
.unwrap();
root.get_child_mut("domain_0").unwrap()
.allocate(ResourceType::Memory, 8192)
.unwrap();
assert_eq!(root.get_child("domain_0").unwrap().used().memory_bytes, 8192);
let domain = root.get_child("domain_0").unwrap();
assert!(domain.can_allocate(ResourceType::Memory, (1 << 20) - 8192));
assert!(!domain.can_allocate(ResourceType::Memory, (1 << 20) - 8191));
}
#[test]
fn test_ledger_quota_zero() {
let q = LedgerQuota::zero();
assert_eq!(q.memory_bytes, 0);
assert_eq!(q.frame_count, 0);
assert_eq!(q.connection_count, 0);
}
#[test]
fn test_ledger_quota_unlimited() {
let q = LedgerQuota::unlimited();
assert_eq!(q.memory_bytes, u64::MAX);
assert_eq!(q.frame_count, u64::MAX);
assert_eq!(q.connection_count, u64::MAX);
}
#[test]
fn test_ledger_quota_can_allocate_memory() {
let parent = make_quota(1024, 0, 0);
let child_ok = make_quota(512, 0, 0);
let child_exceed = make_quota(2048, 0, 0);
assert!(parent.can_allocate(&child_ok).is_ok());
assert!(parent.can_allocate(&child_exceed).is_err());
}
#[test]
fn test_ledger_quota_can_allocate_frames() {
let parent = make_quota(0, 100, 0);
let child_ok = make_quota(0, 50, 0);
let child_exceed = make_quota(0, 200, 0);
assert!(parent.can_allocate(&child_ok).is_ok());
assert!(parent.can_allocate(&child_exceed).is_err());
}
#[test]
fn test_ledger_quota_can_allocate_connections() {
let parent = make_quota(0, 0, 10);
let child_ok = make_quota(0, 0, 5);
let child_exceed = make_quota(0, 0, 20);
assert!(parent.can_allocate(&child_ok).is_ok());
assert!(parent.can_allocate(&child_exceed).is_err());
}
#[test]
fn test_ledger_quota_can_allocate_exact() {
let parent = make_quota(100, 200, 300);
let child_exact = make_quota(100, 200, 300);
assert!(parent.can_allocate(&child_exact).is_ok());
}
#[test]
fn test_ledger_quota_consume_and_restore() {
let mut q = make_quota(1000, 100, 10);
q.consume(ResourceType::Memory, 500).unwrap();
assert_eq!(q.memory_bytes, 500);
q.restore(ResourceType::Memory, 300).unwrap();
assert_eq!(q.memory_bytes, 800);
}
#[test]
fn test_ledger_quota_consume_underflow() {
let mut q = make_quota(100, 0, 0);
let result = q.consume(ResourceType::Memory, 200);
assert!(result.is_err());
}
#[test]
fn test_ledger_quota_restore_overflow() {
let mut q = make_quota(u64::MAX, 0, 0);
let result = q.restore(ResourceType::Memory, 1);
assert!(result.is_err());
}
#[test]
fn test_ledger_quota_get() {
let q = make_quota(10, 20, 30);
assert_eq!(q.get(ResourceType::Memory), 10);
assert_eq!(q.get(ResourceType::Frames), 20);
assert_eq!(q.get(ResourceType::Connections), 30);
}
#[test]
fn test_ledger_quota_remaining() {
let total = make_quota(1000, 100, 50);
let used = make_quota(400, 30, 10);
let remaining = total.remaining(&used);
assert_eq!(remaining.memory_bytes, 600);
assert_eq!(remaining.frame_count, 70);
assert_eq!(remaining.connection_count, 40);
}
#[test]
fn test_ledger_quota_remaining_saturating() {
let total = make_quota(100, 0, 0);
let used = make_quota(200, 0, 0);
let remaining = total.remaining(&used);
assert_eq!(remaining.memory_bytes, 0);
}
#[test]
fn test_ledger_type_depth() {
assert_eq!(LedgerType::Root.depth(), 0);
assert_eq!(LedgerType::Domain.depth(), 1);
assert_eq!(LedgerType::Queue.depth(), 2);
assert_eq!(LedgerType::Connection.depth(), 3);
}
#[test]
fn test_ledger_type_equality() {
assert_eq!(LedgerType::Root, LedgerType::Root);
assert_ne!(LedgerType::Root, LedgerType::Domain);
}
#[test]
fn test_ledger_type_debug() {
assert_eq!(format!("{:?}", LedgerType::Root), "Root");
assert_eq!(format!("{:?}", LedgerType::Connection), "Connection");
}
#[test]
fn test_resource_type_name() {
assert_eq!(ResourceType::Memory.name(), "memory");
assert_eq!(ResourceType::Frames.name(), "frames");
assert_eq!(ResourceType::Connections.name(), "connections");
}
#[test]
fn test_resource_type_equality() {
assert_eq!(ResourceType::Memory, ResourceType::Memory);
assert_ne!(ResourceType::Memory, ResourceType::Frames);
}
#[test]
fn test_resource_type_debug() {
assert_eq!(format!("{:?}", ResourceType::Memory), "Memory");
assert_eq!(format!("{:?}", ResourceType::Frames), "Frames");
}
#[test]
fn test_multiple_children_quota_sum() {
let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
root.create_child("d0", LedgerType::Domain, make_quota(3000, 300, 30)).unwrap();
root.create_child("d1", LedgerType::Domain, make_quota(2000, 200, 20)).unwrap();
root.create_child("d2", LedgerType::Domain, make_quota(5000, 500, 50)).unwrap();
let allocated = root.allocated_to_children();
assert_eq!(allocated.memory_bytes, 10000);
assert_eq!(allocated.frame_count, 1000);
assert_eq!(allocated.connection_count, 100);
}
#[test]
fn test_nested_three_level_quota_conservation() {
let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
root.create_child("d0", LedgerType::Domain, make_quota(5000, 500, 50)).unwrap();
root.get_child_mut("d0").unwrap()
.create_child("q0", LedgerType::Queue, make_quota(2000, 200, 20)).unwrap();
root.get_child_mut("d0").unwrap()
.create_child("q1", LedgerType::Queue, make_quota(3000, 300, 30)).unwrap();
let domain_allocated = root.get_child("d0").unwrap().allocated_to_children();
assert_eq!(domain_allocated.memory_bytes, 5000);
assert_eq!(domain_allocated.frame_count, 500);
assert_eq!(domain_allocated.connection_count, 50);
let root_allocated = root.allocated_to_children();
assert_eq!(root_allocated.memory_bytes, 5000);
}
#[test]
fn test_child_quota_exceeds_parent_total() {
let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
let result = root.create_child("big", LedgerType::Domain, make_quota(2000, 200, 20));
assert!(result.is_err());
assert_eq!(root.child_count(), 0);
assert_eq!(root.allocated_to_children().memory_bytes, 0);
}
#[test]
fn test_child_quota_exceeds_parent_used_remaining() {
let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
root.allocate(ResourceType::Memory, 600).unwrap();
root.allocate(ResourceType::Frames, 60).unwrap();
root.allocate(ResourceType::Connections, 6).unwrap();
let result = root.create_child("d0", LedgerType::Domain, make_quota(500, 50, 5));
assert!(result.is_err());
assert_eq!(root.child_count(), 0);
}
#[test]
fn test_release_underflow_memory() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1000, 0, 0));
ledger.allocate(ResourceType::Memory, 500).unwrap();
ledger.release(ResourceType::Memory, 300).unwrap();
assert_eq!(ledger.used().memory_bytes, 200);
}
#[test]
fn test_release_underflow_frames() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 100, 0));
ledger.allocate(ResourceType::Frames, 50).unwrap();
let result = ledger.release(ResourceType::Frames, 100);
assert!(result.is_err());
}
#[test]
fn test_release_underflow_connections() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(0, 0, 10));
let result = ledger.release(ResourceType::Connections, 1);
assert!(result.is_err());
}
#[test]
fn test_allocated_to_children_after_remove() {
let mut root = ResourceLedger::root(make_quota(10000, 1000, 100));
root.create_child("d0", LedgerType::Domain, make_quota(3000, 300, 30)).unwrap();
root.create_child("d1", LedgerType::Domain, make_quota(2000, 200, 20)).unwrap();
assert_eq!(root.allocated_to_children().memory_bytes, 5000);
assert_eq!(root.allocated_to_children().frame_count, 500);
assert_eq!(root.allocated_to_children().connection_count, 50);
root.remove_child("d0").unwrap();
assert_eq!(root.allocated_to_children().memory_bytes, 2000);
assert_eq!(root.allocated_to_children().frame_count, 200);
assert_eq!(root.allocated_to_children().connection_count, 20);
}
#[test]
fn test_allocated_to_children_zero_initially() {
let root = ResourceLedger::root(make_quota(1000, 100, 10));
assert_eq!(root.allocated_to_children().memory_bytes, 0);
assert_eq!(root.allocated_to_children().frame_count, 0);
assert_eq!(root.allocated_to_children().connection_count, 0);
}
#[test]
fn test_ledger_quota_default() {
let q = LedgerQuota::default();
assert_eq!(q.memory_bytes, 0);
assert_eq!(q.frame_count, 0);
assert_eq!(q.connection_count, 0);
}
#[test]
fn test_ledger_remaining() {
let mut ledger = ResourceLedger::new("test", LedgerType::Queue, make_quota(1000, 100, 10));
ledger.allocate(ResourceType::Memory, 400).unwrap();
ledger.allocate(ResourceType::Frames, 30).unwrap();
ledger.allocate(ResourceType::Connections, 5).unwrap();
let remaining = ledger.remaining();
assert_eq!(remaining.memory_bytes, 600);
assert_eq!(remaining.frame_count, 70);
assert_eq!(remaining.connection_count, 5);
}
#[test]
fn test_four_level_hierarchy_allocated_to_children() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("d0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000)).unwrap();
root.get_child_mut("d0").unwrap()
.create_child("q0", LedgerType::Queue, make_quota(1 << 15, 1_000, 100)).unwrap();
root.get_child_mut("d0").unwrap().get_child_mut("q0").unwrap()
.create_child("c0", LedgerType::Connection, make_quota(1 << 10, 100, 10)).unwrap();
assert_eq!(root.max_depth(), 3);
assert_eq!(root.node_count(), 4);
let conn = root.get_child("d0").unwrap().get_child("q0").unwrap().get_child("c0").unwrap();
assert_eq!(conn.ledger_type(), LedgerType::Connection);
assert_eq!(conn.depth(), 3);
}
#[test]
fn test_create_child_oversubscription_rejected() {
let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
root.create_child("c1", LedgerType::Domain, make_quota(600, 60, 6)).unwrap();
let result = root.create_child("c2", LedgerType::Domain, make_quota(600, 0, 0));
assert!(result.is_err());
assert_eq!(root.child_count(), 1);
root.create_child("c2", LedgerType::Domain, make_quota(400, 40, 4)).unwrap();
assert_eq!(root.child_count(), 2);
assert_eq!(root.allocated_to_children().memory_bytes, 1000);
assert!(root.create_child("c3", LedgerType::Domain, make_quota(1, 0, 0)).is_err());
}
#[test]
fn test_create_child_accounts_parent_used() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.allocate(ResourceType::Memory, 400).unwrap();
root.create_child("c1", LedgerType::Domain, make_quota(600, 0, 0)).unwrap();
assert!(root.create_child("c2", LedgerType::Domain, make_quota(1, 0, 0)).is_err());
}
#[test]
fn test_allocate_in_child_rolls_up_to_ancestors() {
let mut root = ResourceLedger::root(make_quota(1 << 30, 100_000, 10_000));
root.create_child("d0", LedgerType::Domain, make_quota(1 << 20, 10_000, 1_000)).unwrap();
root.get_child_mut("d0").unwrap()
.create_child("q0", LedgerType::Queue, make_quota(1 << 15, 1_000, 100)).unwrap();
root.allocate_in_child(&["d0", "q0"], ResourceType::Memory, 1024).unwrap();
assert_eq!(root.used().memory_bytes, 1024);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 1024);
assert_eq!(
root.get_child("d0").unwrap().get_child("q0").unwrap().used().memory_bytes,
1024
);
root.release_in_child(&["d0", "q0"], ResourceType::Memory, 1024).unwrap();
assert_eq!(root.used().memory_bytes, 0);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
assert_eq!(
root.get_child("d0").unwrap().get_child("q0").unwrap().used().memory_bytes,
0
);
}
#[test]
fn test_allocate_in_child_ancestor_quota_enforced() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
assert!(root.allocate(ResourceType::Memory, 600).is_err());
root.allocate(ResourceType::Memory, 500).unwrap();
root.allocate_in_child(&["d0"], ResourceType::Memory, 500).unwrap();
assert_eq!(root.used().memory_bytes, 1000);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 500);
let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 100);
assert!(result.is_err());
assert_eq!(root.used().memory_bytes, 1000);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 500);
}
#[test]
fn test_allocate_in_child_rollback_on_descendant_failure() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 600);
assert!(result.is_err());
assert_eq!(root.used().memory_bytes, 0);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
}
#[test]
fn test_allocate_not_double_counted_with_child_usage() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
assert_eq!(root.used().memory_bytes, 200);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
root.allocate(ResourceType::Memory, 400).unwrap();
assert_eq!(root.used().memory_bytes, 600);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
assert_eq!(root.remaining().memory_bytes, 400);
}
#[test]
fn test_allocate_in_child_rollback_does_not_touch_used_direct() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
root.allocate(ResourceType::Memory, 300).unwrap();
let result = root.allocate_in_child(&["d0"], ResourceType::Memory, 600);
assert!(result.is_err());
assert_eq!(root.used().memory_bytes, 300);
}
#[test]
fn test_allocate_in_child_unknown_path() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
assert!(root.allocate_in_child(&["nope"], ResourceType::Memory, 100).is_err());
assert!(root.allocate_in_child(&["d0", "nope"], ResourceType::Memory, 100).is_err());
assert_eq!(root.used().memory_bytes, 0);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 0);
}
#[test]
fn test_release_in_child_restores_on_descendant_failure() {
let mut root = ResourceLedger::root(make_quota(1000, 0, 0));
root.create_child("d0", LedgerType::Domain, make_quota(500, 0, 0)).unwrap();
root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
let result = root.release_in_child(&["d0"], ResourceType::Memory, 300);
assert!(result.is_err());
assert_eq!(root.used().memory_bytes, 200);
assert_eq!(root.get_child("d0").unwrap().used().memory_bytes, 200);
root.release_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
assert_eq!(root.used().memory_bytes, 0);
}
#[test]
fn test_remove_child_reclaims_rolled_up_usage() {
let mut root = ResourceLedger::root(make_quota(1000, 100, 10));
root.create_child("d0", LedgerType::Domain, make_quota(500, 50, 5)).unwrap();
root.allocate_in_child(&["d0"], ResourceType::Memory, 200).unwrap();
assert_eq!(root.used().memory_bytes, 200);
root.remove_child("d0").unwrap();
assert_eq!(root.used().memory_bytes, 0);
assert_eq!(root.allocated_to_children().memory_bytes, 0);
root.create_child("d1", LedgerType::Domain, make_quota(1000, 100, 10)).unwrap();
}
}