Skip to main content

fakecloud_cloudwatch/
state.rs

1use std::collections::BTreeMap;
2use std::sync::Arc;
3
4use chrono::{DateTime, Utc};
5use parking_lot::RwLock;
6use serde::{Deserialize, Serialize};
7
8pub type SharedCloudWatchState = Arc<RwLock<CloudWatchAccounts>>;
9
10/// On-disk snapshot envelope for CloudWatch state.
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct CloudWatchSnapshot {
13    pub schema_version: u32,
14    pub accounts: CloudWatchAccounts,
15}
16
17pub const CLOUDWATCH_SNAPSHOT_SCHEMA_VERSION: u32 = 1;
18
19#[derive(Debug, Default, Clone, Serialize, Deserialize)]
20pub struct CloudWatchAccounts {
21    pub accounts: BTreeMap<String, CloudWatchState>,
22}
23
24impl CloudWatchAccounts {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Deep-clone for snapshot serialization. `Clone` isn't derived
30    /// because the live state is held behind a `RwLock` and the rest of
31    /// the crate references it by reference — this helper makes the
32    /// intent explicit at the persistence boundary.
33    pub fn clone_for_snapshot(&self) -> CloudWatchAccounts {
34        CloudWatchAccounts {
35            accounts: self.accounts.clone(),
36        }
37    }
38
39    pub fn get_or_create(&mut self, account_id: &str) -> &mut CloudWatchState {
40        self.accounts
41            .entry(account_id.to_string())
42            .or_insert_with(|| CloudWatchState::new(account_id))
43    }
44
45    pub fn get(&self, account_id: &str) -> Option<&CloudWatchState> {
46        self.accounts.get(account_id)
47    }
48}
49
50#[derive(Debug, Default, Clone, Serialize, Deserialize)]
51pub struct CloudWatchState {
52    pub account_id: String,
53    /// region -> namespace -> Vec<MetricDatum>
54    pub metrics: BTreeMap<String, BTreeMap<String, Vec<MetricDatum>>>,
55    /// region -> alarm_name -> MetricAlarm
56    pub alarms: BTreeMap<String, BTreeMap<String, MetricAlarm>>,
57    /// Dashboards keyed by name (CloudWatch dashboards are global per
58    /// account, not regional).
59    #[serde(default)]
60    pub dashboards: BTreeMap<String, Dashboard>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct Dashboard {
65    pub name: String,
66    pub arn: String,
67    pub body: String,
68    pub last_modified: DateTime<Utc>,
69    pub size_bytes: i64,
70}
71
72impl CloudWatchState {
73    pub fn new(account_id: &str) -> Self {
74        Self {
75            account_id: account_id.to_string(),
76            metrics: BTreeMap::new(),
77            alarms: BTreeMap::new(),
78            dashboards: BTreeMap::new(),
79        }
80    }
81
82    pub fn metrics_in(&self, region: &str) -> Option<&BTreeMap<String, Vec<MetricDatum>>> {
83        self.metrics.get(region)
84    }
85
86    pub fn metrics_in_mut(&mut self, region: &str) -> &mut BTreeMap<String, Vec<MetricDatum>> {
87        self.metrics.entry(region.to_string()).or_default()
88    }
89
90    pub fn alarms_in(&self, region: &str) -> Option<&BTreeMap<String, MetricAlarm>> {
91        self.alarms.get(region)
92    }
93
94    pub fn alarms_in_mut(&mut self, region: &str) -> &mut BTreeMap<String, MetricAlarm> {
95        self.alarms.entry(region.to_string()).or_default()
96    }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct MetricDatum {
101    pub metric_name: String,
102    pub dimensions: BTreeMap<String, String>,
103    pub timestamp: DateTime<Utc>,
104    pub value: Option<f64>,
105    pub statistic_values: Option<StatisticSet>,
106    pub unit: Option<String>,
107    pub storage_resolution: Option<i64>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct StatisticSet {
112    pub sample_count: f64,
113    pub sum: f64,
114    pub minimum: f64,
115    pub maximum: f64,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct MetricAlarm {
120    pub alarm_name: String,
121    pub alarm_arn: String,
122    pub alarm_description: Option<String>,
123    pub actions_enabled: bool,
124    pub ok_actions: Vec<String>,
125    pub alarm_actions: Vec<String>,
126    pub insufficient_data_actions: Vec<String>,
127    pub state_value: AlarmState,
128    pub state_reason: String,
129    pub state_updated_timestamp: DateTime<Utc>,
130    pub metric_name: Option<String>,
131    pub namespace: Option<String>,
132    pub statistic: Option<String>,
133    pub extended_statistic: Option<String>,
134    pub dimensions: BTreeMap<String, String>,
135    pub period: Option<i64>,
136    pub unit: Option<String>,
137    pub evaluation_periods: i64,
138    pub datapoints_to_alarm: Option<i64>,
139    pub threshold: Option<f64>,
140    pub comparison_operator: String,
141    pub treat_missing_data: Option<String>,
142    pub evaluate_low_sample_count_percentile: Option<String>,
143    pub configuration_updated_timestamp: DateTime<Utc>,
144    pub alarm_configuration_updated_timestamp: DateTime<Utc>,
145}
146
147#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
148pub enum AlarmState {
149    Ok,
150    Alarm,
151    InsufficientData,
152}
153
154impl AlarmState {
155    pub fn as_str(&self) -> &'static str {
156        match self {
157            AlarmState::Ok => "OK",
158            AlarmState::Alarm => "ALARM",
159            AlarmState::InsufficientData => "INSUFFICIENT_DATA",
160        }
161    }
162
163    pub fn parse(s: &str) -> Option<Self> {
164        match s {
165            "OK" => Some(AlarmState::Ok),
166            "ALARM" => Some(AlarmState::Alarm),
167            "INSUFFICIENT_DATA" => Some(AlarmState::InsufficientData),
168            _ => None,
169        }
170    }
171}