Skip to main content

rskit_workload/
report.rs

1//! Runtime state and result reports returned by a [`crate::Manager`].
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6
7use crate::state::WorkloadState;
8
9/// Returned after a successful deployment.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct DeployResult {
12    /// Provider-assigned identifier (container ID or pod/job name).
13    pub id: String,
14    /// Human-readable name.
15    pub name: String,
16    /// Initial state.
17    pub state: WorkloadState,
18}
19
20/// Returned when a workload exits.
21#[derive(Debug, Clone, Default, PartialEq, Eq)]
22pub struct WaitResult {
23    /// Process exit code.
24    pub status_code: i64,
25    /// Failure reason, when the workload exited abnormally.
26    pub error: Option<String>,
27}
28
29/// Detailed status of a single workload.
30#[derive(Debug, Clone, Default, PartialEq, Eq)]
31pub struct WorkloadStatus {
32    /// Provider-assigned identifier.
33    pub id: String,
34    /// Human-readable name.
35    pub name: String,
36    /// Image reference.
37    pub image: String,
38    /// Lifecycle state.
39    pub state: WorkloadState,
40    /// Whether the workload is currently running.
41    pub running: bool,
42    /// Whether provider health checks pass.
43    pub healthy: bool,
44    /// Whether all readiness checks pass (Kubernetes).
45    pub ready: bool,
46    /// When the workload started, if known.
47    pub started_at: Option<DateTime<Utc>>,
48    /// When the workload stopped, if known.
49    pub stopped_at: Option<DateTime<Utc>>,
50    /// Process exit code, if the workload has exited.
51    pub exit_code: Option<i32>,
52    /// Human-readable status message.
53    pub message: String,
54    /// Restart count (Kubernetes).
55    pub restarts: u32,
56}
57
58/// Summary information returned by list operations.
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub struct WorkloadInfo {
61    /// Provider-assigned identifier.
62    pub id: String,
63    /// Human-readable name.
64    pub name: String,
65    /// Image reference.
66    pub image: String,
67    /// Lifecycle state.
68    pub state: WorkloadState,
69    /// Labels attached to the workload.
70    pub labels: HashMap<String, String>,
71    /// Creation time.
72    pub created: DateTime<Utc>,
73    /// Kubernetes namespace.
74    pub namespace: String,
75}
76
77/// Resource usage statistics.
78#[derive(Debug, Clone, Default, PartialEq)]
79pub struct WorkloadStats {
80    /// CPU usage as a percentage.
81    pub cpu_percent: f64,
82    /// Memory in use, in bytes.
83    pub memory_usage: i64,
84    /// Memory limit, in bytes.
85    pub memory_limit: i64,
86    /// Bytes received over the network.
87    pub network_rx_bytes: i64,
88    /// Bytes transmitted over the network.
89    pub network_tx_bytes: i64,
90    /// Bytes read from disk.
91    pub disk_read_bytes: i64,
92    /// Bytes written to disk.
93    pub disk_write_bytes: i64,
94    /// Number of processes/threads.
95    pub pids: i64,
96}
97
98/// A workload lifecycle event.
99#[derive(Debug, Clone, PartialEq, Eq)]
100pub struct WorkloadEvent {
101    /// Workload identifier.
102    pub id: String,
103    /// Workload name.
104    pub name: String,
105    /// Event kind (e.g. `"start"`, `"stop"`, `"die"`, `"oom"`, `"restart"`).
106    pub event: String,
107    /// When the event occurred.
108    pub timestamp: DateTime<Utc>,
109    /// Human-readable detail.
110    pub message: String,
111}
112
113/// Result of running a command inside a workload.
114#[derive(Debug, Clone, Default, PartialEq, Eq)]
115pub struct ExecResult {
116    /// Command exit code.
117    pub exit_code: i32,
118    /// Captured standard output.
119    pub stdout: String,
120    /// Captured standard error.
121    pub stderr: String,
122}
123
124/// Host-level information about a backend runtime.
125#[derive(Debug, Clone, Default, PartialEq, Eq)]
126pub struct SystemInfo {
127    /// Operating system (e.g. `"linux"`, `"darwin"`).
128    pub os: String,
129    /// CPU architecture (e.g. `"x86_64"`, `"aarch64"`).
130    pub architecture: String,
131    /// Total host memory in mebibytes.
132    pub total_memory_mb: i64,
133    /// Number of logical CPUs.
134    pub cpus: u32,
135    /// Storage driver in use (e.g. `"overlay2"`).
136    pub storage_driver: String,
137    /// Runtime version (e.g. Docker/containerd version).
138    pub runtime_version: String,
139    /// Host kernel version.
140    pub kernel_version: String,
141    /// Full operating-system name (e.g. `"Ubuntu 24.04"`).
142    pub operating_system: String,
143    /// GPUs detected by the runtime.
144    pub gpus: Vec<GpuInfo>,
145}
146
147/// A GPU detected by a backend runtime.
148#[derive(Debug, Clone, Default, PartialEq, Eq)]
149pub struct GpuInfo {
150    /// GPU model name.
151    pub name: String,
152    /// Total video memory in mebibytes.
153    pub memory_mb: i64,
154    /// GPU driver version.
155    pub driver_version: String,
156}
157
158/// Storage consumption reported by a backend runtime.
159#[derive(Debug, Clone, Default, PartialEq, Eq)]
160pub struct DiskUsage {
161    /// Total size of all images in bytes (may include shared layers).
162    pub images_size: i64,
163    /// Total size of all workloads/containers in bytes.
164    pub containers_size: i64,
165    /// Total size of all volumes in bytes.
166    pub volumes_size: i64,
167    /// Total build-cache size in bytes.
168    pub build_cache_size: i64,
169    /// Runtime data-root path (best effort; empty for remote daemons).
170    pub data_root_path: String,
171    /// Total filesystem capacity of the data root in bytes (`0` if unavailable).
172    pub data_root_total: i64,
173    /// Free filesystem space of the data root in bytes (`0` if unavailable).
174    pub data_root_free: i64,
175    /// Per-image disk usage breakdown.
176    pub images: Vec<ImageDiskEntry>,
177}
178
179/// A single image in a [`DiskUsage`] breakdown.
180#[derive(Debug, Clone, PartialEq, Eq)]
181pub struct ImageDiskEntry {
182    /// Image identifier.
183    pub id: String,
184    /// Repository tags pointing at the image.
185    pub repo_tags: Vec<String>,
186    /// Unique size in bytes (excluding shared layers).
187    pub size: i64,
188    /// Size in bytes shared with other images.
189    pub shared_size: i64,
190    /// Image creation time.
191    pub created: DateTime<Utc>,
192}
193
194/// Detailed metadata for a single image.
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub struct ImageDetail {
197    /// Image identifier.
198    pub id: String,
199    /// Repository tags pointing at the image.
200    pub repo_tags: Vec<String>,
201    /// Repository digests for the image.
202    pub repo_digests: Vec<String>,
203    /// Image size in bytes.
204    pub size: i64,
205    /// Image CPU architecture.
206    pub architecture: String,
207    /// Image operating system.
208    pub os: String,
209    /// Image creation time.
210    pub created: DateTime<Utc>,
211    /// Selected container configuration baked into the image.
212    pub config: ImageConfig,
213    /// Layer digests, base to top.
214    pub layers: Vec<String>,
215}
216
217/// Selected container configuration held in an image.
218#[derive(Debug, Clone, Default, PartialEq, Eq)]
219pub struct ImageConfig {
220    /// Default environment variables.
221    pub env: Vec<String>,
222    /// Image labels.
223    pub labels: HashMap<String, String>,
224    /// Default command.
225    pub cmd: Vec<String>,
226    /// Default entrypoint.
227    pub entrypoint: Vec<String>,
228}
229
230/// An image lifecycle event reported by a backend runtime.
231#[derive(Debug, Clone, PartialEq, Eq)]
232pub struct ImageEvent {
233    /// Event action (e.g. `"pull"`, `"delete"`, `"tag"`, `"untag"`, `"import"`).
234    pub action: String,
235    /// Best-effort image reference.
236    pub image_ref: String,
237    /// Image identifier.
238    pub image_id: String,
239    /// When the event occurred.
240    pub timestamp: DateTime<Utc>,
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn status_default_state_is_unknown() {
249        let status = WorkloadStatus::default();
250        assert_eq!(status.state, WorkloadState::Unknown);
251        assert!(status.started_at.is_none());
252        assert!(status.exit_code.is_none());
253    }
254
255    #[test]
256    fn deploy_result_carries_initial_state() {
257        let result = DeployResult {
258            id: "abc123".into(),
259            name: "api".into(),
260            state: WorkloadState::Running,
261        };
262        assert!(result.state.is_running());
263    }
264
265    #[test]
266    fn wait_result_default_is_success() {
267        let result = WaitResult::default();
268        assert_eq!(result.status_code, 0);
269        assert!(result.error.is_none());
270    }
271
272    #[test]
273    fn system_info_default_is_empty() {
274        let info = SystemInfo::default();
275        assert_eq!(info.cpus, 0);
276        assert!(info.gpus.is_empty());
277    }
278
279    #[test]
280    fn disk_usage_carries_image_breakdown() {
281        let usage = DiskUsage {
282            images_size: 2048,
283            images: vec![ImageDiskEntry {
284                id: "img1".into(),
285                repo_tags: vec!["nginx:latest".into()],
286                size: 1024,
287                shared_size: 512,
288                created: Utc::now(),
289            }],
290            ..Default::default()
291        };
292        assert_eq!(usage.images.len(), 1);
293        assert_eq!(usage.images[0].size, 1024);
294    }
295
296    #[test]
297    fn image_detail_holds_config_and_layers() {
298        let detail = ImageDetail {
299            id: "img1".into(),
300            repo_tags: vec!["nginx:latest".into()],
301            repo_digests: Vec::new(),
302            size: 4096,
303            architecture: "amd64".into(),
304            os: "linux".into(),
305            created: Utc::now(),
306            config: ImageConfig {
307                env: vec!["PATH=/usr/bin".into()],
308                ..Default::default()
309            },
310            layers: vec!["sha256:layer".into()],
311        };
312        assert_eq!(detail.config.env.len(), 1);
313        assert_eq!(detail.layers.len(), 1);
314    }
315
316    #[test]
317    fn image_event_records_action() {
318        let event = ImageEvent {
319            action: "pull".into(),
320            image_ref: "nginx:latest".into(),
321            image_id: "sha256:abc".into(),
322            timestamp: Utc::now(),
323        };
324        assert_eq!(event.action, "pull");
325    }
326
327    #[test]
328    fn gpu_info_default_is_empty() {
329        assert_eq!(GpuInfo::default().memory_mb, 0);
330    }
331}