fleetflow_atom/
model.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5/// Flow - プロセスの設計図
6///
7/// Flowは複数のサービスとステージを定義し、
8/// それらがどのように起動・管理されるかを記述します。
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Flow {
11    /// Flow名(プロジェクト名)
12    pub name: String,
13    /// このFlowで定義されるサービス
14    pub services: HashMap<String, Service>,
15    /// このFlowで定義されるステージ
16    pub stages: HashMap<String, Stage>,
17}
18
19/// ステージ定義
20#[derive(Debug, Clone, Default, Serialize, Deserialize)]
21pub struct Stage {
22    /// このステージで起動するサービスのリスト
23    #[serde(default)]
24    pub services: Vec<String>,
25    /// ステージ固有の環境変数
26    #[serde(default)]
27    pub variables: HashMap<String, String>,
28}
29
30/// サービス定義
31#[derive(Debug, Clone, Default, Serialize, Deserialize)]
32pub struct Service {
33    pub image: Option<String>,
34    pub version: Option<String>,
35    pub command: Option<String>,
36    #[serde(default)]
37    pub ports: Vec<Port>,
38    #[serde(default)]
39    pub environment: HashMap<String, String>,
40    #[serde(default)]
41    pub volumes: Vec<Volume>,
42    #[serde(default)]
43    pub depends_on: Vec<String>,
44}
45
46/// ポート定義
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct Port {
49    pub host: u16,
50    pub container: u16,
51    #[serde(default = "default_protocol")]
52    pub protocol: Protocol,
53    pub host_ip: Option<String>,
54}
55
56/// プロトコル種別
57#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
58#[serde(rename_all = "lowercase")]
59pub enum Protocol {
60    #[default]
61    Tcp,
62    Udp,
63}
64
65fn default_protocol() -> Protocol {
66    Protocol::Tcp
67}
68
69/// ボリューム定義
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct Volume {
72    pub host: PathBuf,
73    pub container: PathBuf,
74    #[serde(default)]
75    pub read_only: bool,
76}
77
78/// Process - 実行中のプロセス情報
79///
80/// Flowから起動された実際のプロセス(コンテナ)の状態を表します。
81/// DBに格納され、実行中のプロセスを追跡・管理します。
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct Process {
84    /// プロセスID(UUID)
85    pub id: String,
86    /// 関連するFlow名
87    pub flow_name: String,
88    /// 関連するステージ名
89    pub stage_name: String,
90    /// サービス名
91    pub service_name: String,
92    /// コンテナID(Docker/Podman)
93    pub container_id: Option<String>,
94    /// プロセスID(OS)
95    pub pid: Option<u32>,
96    /// プロセス状態
97    pub state: ProcessState,
98    /// 起動時刻(Unix timestamp)
99    pub started_at: i64,
100    /// 停止時刻(Unix timestamp、停止していない場合はNone)
101    pub stopped_at: Option<i64>,
102    /// イメージ名
103    pub image: String,
104    /// 使用メモリ(バイト)
105    pub memory_usage: Option<u64>,
106    /// CPU使用率(パーセント)
107    pub cpu_usage: Option<f64>,
108    /// ポートマッピング
109    pub ports: Vec<Port>,
110}
111
112/// プロセス状態
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
114#[serde(rename_all = "lowercase")]
115pub enum ProcessState {
116    /// 起動中
117    Starting,
118    /// 実行中
119    Running,
120    /// 停止中
121    Stopping,
122    /// 停止済み
123    Stopped,
124    /// 一時停止
125    Paused,
126    /// 異常終了
127    Failed,
128    /// 再起動中
129    Restarting,
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_flow_creation() {
138        let mut services = HashMap::new();
139        services.insert(
140            "api".to_string(),
141            Service {
142                image: Some("myapp:1.0.0".to_string()),
143                ..Default::default()
144            },
145        );
146
147        let mut stages = HashMap::new();
148        stages.insert(
149            "local".to_string(),
150            Stage {
151                services: vec!["api".to_string()],
152                variables: HashMap::new(),
153            },
154        );
155
156        let flow = Flow {
157            name: "my-project".to_string(),
158            services,
159            stages,
160        };
161
162        assert_eq!(flow.name, "my-project");
163        assert_eq!(flow.services.len(), 1);
164        assert_eq!(flow.stages.len(), 1);
165        assert!(flow.services.contains_key("api"));
166        assert!(flow.stages.contains_key("local"));
167    }
168
169    #[test]
170    fn test_flow_to_flowconfig_conversion() {
171        let mut services = HashMap::new();
172        services.insert("db".to_string(), Service::default());
173
174        let mut stages = HashMap::new();
175        stages.insert("dev".to_string(), Stage::default());
176
177        let flow = Flow {
178            name: "test-flow".to_string(),
179            services: services.clone(),
180            stages: stages.clone(),
181        };
182
183        assert_eq!(flow.services.len(), 1);
184        assert_eq!(flow.stages.len(), 1);
185        assert!(flow.services.contains_key("db"));
186        assert!(flow.stages.contains_key("dev"));
187    }
188
189    #[test]
190    fn test_process_creation() {
191        let process = Process {
192            id: "proc-123".to_string(),
193            flow_name: "my-flow".to_string(),
194            stage_name: "local".to_string(),
195            service_name: "api".to_string(),
196            container_id: Some("container-abc".to_string()),
197            pid: Some(1234),
198            state: ProcessState::Running,
199            started_at: 1704067200,
200            stopped_at: None,
201            image: "myapp:1.0.0".to_string(),
202            memory_usage: Some(256_000_000),
203            cpu_usage: Some(5.5),
204            ports: vec![],
205        };
206
207        assert_eq!(process.id, "proc-123");
208        assert_eq!(process.flow_name, "my-flow");
209        assert_eq!(process.state, ProcessState::Running);
210        assert_eq!(process.pid, Some(1234));
211        assert!(process.stopped_at.is_none());
212    }
213
214    #[test]
215    fn test_process_state_transitions() {
216        let states = vec![
217            ProcessState::Starting,
218            ProcessState::Running,
219            ProcessState::Stopping,
220            ProcessState::Stopped,
221            ProcessState::Paused,
222            ProcessState::Failed,
223            ProcessState::Restarting,
224        ];
225
226        for state in states {
227            let process = Process {
228                id: "test".to_string(),
229                flow_name: "test".to_string(),
230                stage_name: "test".to_string(),
231                service_name: "test".to_string(),
232                container_id: None,
233                pid: None,
234                state: state.clone(),
235                started_at: 0,
236                stopped_at: None,
237                image: "test".to_string(),
238                memory_usage: None,
239                cpu_usage: None,
240                ports: vec![],
241            };
242
243            assert_eq!(process.state, state);
244        }
245    }
246
247    #[test]
248    fn test_process_with_resource_usage() {
249        let process = Process {
250            id: "proc-456".to_string(),
251            flow_name: "resource-test".to_string(),
252            stage_name: "local".to_string(),
253            service_name: "db".to_string(),
254            container_id: Some("container-xyz".to_string()),
255            pid: Some(5678),
256            state: ProcessState::Running,
257            started_at: 1704067200,
258            stopped_at: None,
259            image: "postgres:16".to_string(),
260            memory_usage: Some(512_000_000), // 512MB
261            cpu_usage: Some(10.5),           // 10.5%
262            ports: vec![Port {
263                host: 5432,
264                container: 5432,
265                protocol: Protocol::Tcp,
266                host_ip: None,
267            }],
268        };
269
270        assert_eq!(process.memory_usage, Some(512_000_000));
271        assert_eq!(process.cpu_usage, Some(10.5));
272        assert_eq!(process.ports.len(), 1);
273        assert_eq!(process.ports[0].host, 5432);
274    }
275
276    #[test]
277    fn test_process_serialization() {
278        let process = Process {
279            id: "proc-789".to_string(),
280            flow_name: "serialize-test".to_string(),
281            stage_name: "local".to_string(),
282            service_name: "api".to_string(),
283            container_id: Some("container-123".to_string()),
284            pid: Some(9999),
285            state: ProcessState::Running,
286            started_at: 1704067200,
287            stopped_at: None,
288            image: "myapp:latest".to_string(),
289            memory_usage: Some(128_000_000),
290            cpu_usage: Some(2.5),
291            ports: vec![],
292        };
293
294        // JSON シリアライズ
295        let json = serde_json::to_string(&process).unwrap();
296        assert!(json.contains("proc-789"));
297        assert!(json.contains("serialize-test"));
298
299        // JSON デシリアライズ
300        let deserialized: Process = serde_json::from_str(&json).unwrap();
301        assert_eq!(deserialized.id, process.id);
302        assert_eq!(deserialized.flow_name, process.flow_name);
303        assert_eq!(deserialized.state, process.state);
304    }
305}