Skip to main content

ferrum_interfaces/vnext/resource/
maintenance_boundary.rs

1use serde::Serialize;
2
3use super::{
4    BackingChunkIdentity, CapacityDomainId, CapacityVector, DynamicBackingPackingEnvelope,
5    DynamicBackingPoolId, DynamicPoolLiveOccupancyStatus, LogicalAdmissionCoordinatorId,
6};
7use crate::vnext::DeviceCapacityPressure;
8
9pub const DYNAMIC_POOL_MAINTENANCE_BOUNDARY_SCHEMA_VERSION: u32 = 1;
10
11/// One resident chunk as observed while every pool maintenance/state lock is
12/// held and before a pressure-driven rebalance mutates residency.
13#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
14pub struct DynamicPoolMaintenanceBoundaryChunk {
15    pub(in crate::vnext::resource) identity: BackingChunkIdentity,
16    pub(in crate::vnext::resource) bytes: u64,
17    pub(in crate::vnext::resource) live_segments: u64,
18    pub(in crate::vnext::resource) external_references: usize,
19    pub(in crate::vnext::resource) protected_packing: bool,
20    pub(in crate::vnext::resource) full_extent_available: bool,
21    pub(in crate::vnext::resource) resident_floor_allows_reclaim: bool,
22    pub(in crate::vnext::resource) reclaim_candidate: bool,
23}
24
25impl DynamicPoolMaintenanceBoundaryChunk {
26    pub fn identity(&self) -> &BackingChunkIdentity {
27        &self.identity
28    }
29
30    pub const fn bytes(&self) -> u64 {
31        self.bytes
32    }
33
34    pub const fn live_segments(&self) -> u64 {
35        self.live_segments
36    }
37
38    pub const fn external_references(&self) -> usize {
39        self.external_references
40    }
41
42    pub const fn protected_packing(&self) -> bool {
43        self.protected_packing
44    }
45
46    pub const fn full_extent_available(&self) -> bool {
47        self.full_extent_available
48    }
49
50    pub const fn resident_floor_allows_reclaim(&self) -> bool {
51        self.resident_floor_allows_reclaim
52    }
53
54    pub const fn reclaim_candidate(&self) -> bool {
55        self.reclaim_candidate
56    }
57}
58
59/// Event-bound physical and logical state for one pool at a failed device
60/// reservation. Consumers can recompute the complete reclaim frontier instead
61/// of inferring it from a later health snapshot.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
63pub struct DynamicPoolMaintenanceBoundaryPool {
64    pub(in crate::vnext::resource) pool_id: DynamicBackingPoolId,
65    pub(in crate::vnext::resource) domain_id: CapacityDomainId,
66    pub(in crate::vnext::resource) excluded_from_reclaim: bool,
67    pub(in crate::vnext::resource) resident_bytes: u64,
68    pub(in crate::vnext::resource) pending_growth_bytes: u64,
69    pub(in crate::vnext::resource) free_bytes: u64,
70    pub(in crate::vnext::resource) largest_contiguous_bytes: u64,
71    pub(in crate::vnext::resource) free_extent_layout_fingerprint: String,
72    pub(in crate::vnext::resource) logical_used_bytes: u64,
73    pub(in crate::vnext::resource) live_occupancy: DynamicPoolLiveOccupancyStatus,
74    pub(in crate::vnext::resource) minimum_resident_bytes: u64,
75    pub(in crate::vnext::resource) maximum_resident_bytes: u64,
76    pub(in crate::vnext::resource) protected_immediate_bytes: u64,
77    pub(in crate::vnext::resource) protected_packing_satisfied: bool,
78    /// A fully idle single-claim target can retain its previous largest extent
79    /// while replacing redundant smaller chunks. The pending larger claim
80    /// still requires ordinary budget reservation; it is not currently packed.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub(in crate::vnext::resource) idle_target_replacement_kept_chunk: Option<BackingChunkIdentity>,
83    pub(in crate::vnext::resource) coherent_runnable_floor_bytes: u64,
84    pub(in crate::vnext::resource) resident_floor_bytes: u64,
85    pub(in crate::vnext::resource) reclaimable_bytes: u64,
86    pub(in crate::vnext::resource) chunks: Vec<DynamicPoolMaintenanceBoundaryChunk>,
87}
88
89impl DynamicPoolMaintenanceBoundaryPool {
90    pub fn pool_id(&self) -> &DynamicBackingPoolId {
91        &self.pool_id
92    }
93
94    pub const fn domain_id(&self) -> CapacityDomainId {
95        self.domain_id
96    }
97
98    pub const fn excluded_from_reclaim(&self) -> bool {
99        self.excluded_from_reclaim
100    }
101
102    pub const fn resident_bytes(&self) -> u64 {
103        self.resident_bytes
104    }
105
106    pub const fn pending_growth_bytes(&self) -> u64 {
107        self.pending_growth_bytes
108    }
109
110    pub const fn free_bytes(&self) -> u64 {
111        self.free_bytes
112    }
113
114    pub const fn largest_contiguous_bytes(&self) -> u64 {
115        self.largest_contiguous_bytes
116    }
117
118    pub fn free_extent_layout_fingerprint(&self) -> &str {
119        &self.free_extent_layout_fingerprint
120    }
121
122    pub const fn logical_used_bytes(&self) -> u64 {
123        self.logical_used_bytes
124    }
125
126    pub const fn live_occupancy(&self) -> &DynamicPoolLiveOccupancyStatus {
127        &self.live_occupancy
128    }
129
130    pub const fn minimum_resident_bytes(&self) -> u64 {
131        self.minimum_resident_bytes
132    }
133
134    pub const fn maximum_resident_bytes(&self) -> u64 {
135        self.maximum_resident_bytes
136    }
137
138    pub const fn protected_immediate_bytes(&self) -> u64 {
139        self.protected_immediate_bytes
140    }
141
142    pub const fn protected_packing_satisfied(&self) -> bool {
143        self.protected_packing_satisfied
144    }
145
146    pub fn idle_target_replacement_kept_chunk(&self) -> Option<&BackingChunkIdentity> {
147        self.idle_target_replacement_kept_chunk.as_ref()
148    }
149
150    pub const fn coherent_runnable_floor_bytes(&self) -> u64 {
151        self.coherent_runnable_floor_bytes
152    }
153
154    pub const fn resident_floor_bytes(&self) -> u64 {
155        self.resident_floor_bytes
156    }
157
158    pub const fn reclaimable_bytes(&self) -> u64 {
159        self.reclaimable_bytes
160    }
161
162    pub fn chunks(&self) -> &[DynamicPoolMaintenanceBoundaryChunk] {
163        &self.chunks
164    }
165}
166
167/// Atomic cold-path receipt for the exact capacity boundary that caused a
168/// rebalance attempt. Selected chunks describe the planner decision before
169/// mutation; `reclaim_sufficient=false` is the typed reason maintenance must
170/// wait for a release epoch instead of retrying allocation.
171#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
172pub struct DynamicPoolMaintenanceBoundaryReceipt {
173    pub(in crate::vnext::resource) schema_version: u32,
174    pub(in crate::vnext::resource) coordinator_id: LogicalAdmissionCoordinatorId,
175    pub(in crate::vnext::resource) logical_release_epoch: u64,
176    pub(in crate::vnext::resource) logical_capacity_epoch: u64,
177    pub(in crate::vnext::resource) plan_device_capacity_epoch: u64,
178    pub(in crate::vnext::resource) process_device_capacity_epoch: u64,
179    pub(in crate::vnext::resource) pressure: DeviceCapacityPressure,
180    pub(in crate::vnext::resource) planned_domains: Vec<CapacityDomainId>,
181    pub(in crate::vnext::resource) protected_immediate: CapacityVector,
182    pub(in crate::vnext::resource) protected_packing_envelopes: Vec<DynamicBackingPackingEnvelope>,
183    pub(in crate::vnext::resource) pools: Vec<DynamicPoolMaintenanceBoundaryPool>,
184    pub(in crate::vnext::resource) reclaim_candidate_chunks: usize,
185    pub(in crate::vnext::resource) reclaim_candidate_bytes: u64,
186    pub(in crate::vnext::resource) selected_chunks: Vec<BackingChunkIdentity>,
187    pub(in crate::vnext::resource) selected_bytes: u64,
188    pub(in crate::vnext::resource) reclaim_sufficient: bool,
189}
190
191impl DynamicPoolMaintenanceBoundaryReceipt {
192    pub const fn schema_version(&self) -> u32 {
193        self.schema_version
194    }
195
196    pub const fn coordinator_id(&self) -> LogicalAdmissionCoordinatorId {
197        self.coordinator_id
198    }
199
200    pub const fn logical_release_epoch(&self) -> u64 {
201        self.logical_release_epoch
202    }
203
204    pub const fn logical_capacity_epoch(&self) -> u64 {
205        self.logical_capacity_epoch
206    }
207
208    pub const fn plan_device_capacity_epoch(&self) -> u64 {
209        self.plan_device_capacity_epoch
210    }
211
212    pub const fn process_device_capacity_epoch(&self) -> u64 {
213        self.process_device_capacity_epoch
214    }
215
216    pub const fn pressure(&self) -> &DeviceCapacityPressure {
217        &self.pressure
218    }
219
220    pub fn planned_domains(&self) -> &[CapacityDomainId] {
221        &self.planned_domains
222    }
223
224    pub const fn protected_immediate(&self) -> &CapacityVector {
225        &self.protected_immediate
226    }
227
228    pub fn protected_packing_envelopes(&self) -> &[DynamicBackingPackingEnvelope] {
229        &self.protected_packing_envelopes
230    }
231
232    pub fn pools(&self) -> &[DynamicPoolMaintenanceBoundaryPool] {
233        &self.pools
234    }
235
236    pub const fn reclaim_candidate_chunks(&self) -> usize {
237        self.reclaim_candidate_chunks
238    }
239
240    pub const fn reclaim_candidate_bytes(&self) -> u64 {
241        self.reclaim_candidate_bytes
242    }
243
244    pub fn selected_chunks(&self) -> &[BackingChunkIdentity] {
245        &self.selected_chunks
246    }
247
248    pub const fn selected_bytes(&self) -> u64 {
249        self.selected_bytes
250    }
251
252    pub const fn reclaim_sufficient(&self) -> bool {
253        self.reclaim_sufficient
254    }
255}