osc_cost/
core.rs

1use serde::Deserialize;
2use serde::Serialize;
3use std::collections::HashMap;
4use std::error;
5use std::fmt;
6use strum_macros::EnumString;
7
8use self::dedicated_instances::DedicatedInstance;
9use self::flexible_gpus::FlexibleGpu;
10use self::load_balancers::LoadBalancer;
11use self::nat_services::NatServices;
12use self::oos::Oos;
13use self::public_ips::PublicIp;
14use self::snapshots::Snapshot;
15use self::vms::Vm;
16use self::volumes::Volume;
17use self::vpn::Vpn;
18
19static HOURS_PER_MONTH: f32 = (365_f32 * 24_f32) / 12_f32;
20
21pub mod dedicated_instances;
22pub mod digest;
23pub mod flexible_gpus;
24pub mod load_balancers;
25pub mod nat_services;
26pub mod oos;
27pub mod public_ips;
28pub mod snapshots;
29pub mod vms;
30pub mod volumes;
31pub mod vpn;
32
33#[derive(Serialize, Deserialize, Debug, EnumString)]
34#[serde(tag = "resource_type")]
35pub enum Resource {
36    Vm(Vm),
37    Volume(Volume),
38    PublicIp(PublicIp),
39    Snapshot(Snapshot),
40    NatServices(NatServices),
41    Aggregate(Aggregate),
42    FlexibleGpu(FlexibleGpu),
43    LoadBalancer(LoadBalancer),
44    Vpn(Vpn),
45    Oos(Oos),
46    DedicatedInstance(DedicatedInstance),
47}
48
49#[derive(Debug, Serialize)]
50pub struct Resources {
51    pub resources: Vec<Resource>,
52}
53
54impl Resources {
55    pub fn compute(&mut self) -> Result<(), ResourceError> {
56        for resource in self.resources.iter_mut() {
57            match resource {
58                Resource::Volume(volume) => volume.compute()?,
59                Resource::Vm(vm) => vm.compute()?,
60                Resource::PublicIp(pip) => pip.compute()?,
61                Resource::Snapshot(snapshot) => snapshot.compute()?,
62                Resource::NatServices(nat_service) => nat_service.compute()?,
63                Resource::Aggregate(aggregate) => aggregate.compute()?,
64                Resource::FlexibleGpu(flexible_gpu) => flexible_gpu.compute()?,
65                Resource::LoadBalancer(load_balancer) => load_balancer.compute()?,
66                Resource::Vpn(vpn) => vpn.compute()?,
67                Resource::Oos(oos) => oos.compute()?,
68                Resource::DedicatedInstance(dedicated_instance) => dedicated_instance.compute()?,
69            }
70        }
71        Ok(())
72    }
73
74    pub fn aggregate(self) -> Self {
75        let mut resource_aggregate: HashMap<String, Aggregate> = HashMap::new();
76
77        for resource in self.resources {
78            let aggregate: Aggregate = Aggregate::from(resource);
79            if let Some(cache) = resource_aggregate.get_mut(&aggregate.aggregated_resource_type) {
80                cache.price_per_hour = match cache.price_per_hour {
81                    Some(price) => Some(price + aggregate.price_per_hour.unwrap_or(0.0)),
82                    None => aggregate.price_per_hour,
83                };
84
85                cache.price_per_month = match cache.price_per_month {
86                    Some(price) => Some(price + aggregate.price_per_month.unwrap_or(0.0)),
87                    None => aggregate.price_per_month,
88                };
89
90                cache.count += aggregate.count;
91            } else {
92                resource_aggregate.insert(aggregate.aggregated_resource_type.clone(), aggregate);
93            }
94        }
95
96        let mut result = Resources {
97            resources: Vec::new(),
98        };
99
100        for val in resource_aggregate.values() {
101            result.resources.push(Resource::Aggregate(val.clone()));
102        }
103
104        result
105    }
106
107    pub fn cost_per_hour(&self) -> Result<f32, ResourceError> {
108        let mut total = 0f32;
109        for resource in &self.resources {
110            match resource {
111                Resource::Volume(volume) => {
112                    total += volume.price_per_hour()?;
113                }
114                Resource::Vm(vm) => {
115                    total += vm.price_per_hour()?;
116                }
117                Resource::PublicIp(pip) => {
118                    total += pip.price_per_hour()?;
119                }
120                Resource::Snapshot(snapshot) => {
121                    total += snapshot.price_per_hour()?;
122                }
123                Resource::NatServices(nat_services) => {
124                    total += nat_services.price_per_hour()?;
125                }
126                Resource::Aggregate(aggregade) => {
127                    total += aggregade.price_per_hour()?;
128                }
129                Resource::FlexibleGpu(flexible_gpu) => {
130                    total += flexible_gpu.price_per_hour()?;
131                }
132                Resource::LoadBalancer(load_balancer) => {
133                    total += load_balancer.price_per_hour()?;
134                }
135                Resource::Vpn(vpn) => {
136                    total += vpn.price_per_hour()?;
137                }
138                Resource::Oos(oos) => {
139                    total += oos.price_per_hour()?;
140                }
141                Resource::DedicatedInstance(dedicated_instance) => {
142                    total += dedicated_instance.price_per_hour()?;
143                }
144            }
145        }
146        Ok(total)
147    }
148
149    pub fn cost_per_month(&self) -> Result<f32, ResourceError> {
150        Ok(self.cost_per_hour()? * HOURS_PER_MONTH)
151    }
152
153    pub fn cost_per_year(&self) -> Result<f32, ResourceError> {
154        Ok(self.cost_per_hour()? * HOURS_PER_MONTH * 12.0)
155    }
156}
157
158#[derive(Debug, Clone)]
159pub enum ResourceError {
160    NotComputed,
161}
162
163impl fmt::Display for ResourceError {
164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165        match self {
166            ResourceError::NotComputed => write!(f, "resource price is not computed yet"),
167        }
168    }
169}
170
171impl error::Error for ResourceError {}
172
173trait ResourceTrait {
174    fn price_per_hour(&self) -> Result<f32, ResourceError>;
175    fn compute(&mut self) -> Result<(), ResourceError>;
176}
177
178#[derive(Serialize, Deserialize, Debug, Clone, Default)]
179pub struct Aggregate {
180    pub osc_cost_version: Option<String>,
181    pub account_id: Option<String>,
182    pub read_date_rfc3339: Option<String>,
183    pub region: Option<String>,
184    pub price_per_hour: Option<f32>,
185    pub price_per_month: Option<f32>,
186    pub aggregated_resource_type: String,
187    pub count: i32,
188}
189
190impl ResourceTrait for Aggregate {
191    fn price_per_hour(&self) -> Result<f32, ResourceError> {
192        match self.price_per_hour {
193            Some(price) => Ok(price),
194            None => Err(ResourceError::NotComputed),
195        }
196    }
197
198    fn compute(&mut self) -> Result<(), ResourceError> {
199        Ok(())
200    }
201}
202
203impl From<Resource> for Aggregate {
204    fn from(item: Resource) -> Self {
205        match item {
206            Resource::Vm(vm) => Aggregate {
207                osc_cost_version: vm.osc_cost_version,
208                account_id: vm.account_id,
209                read_date_rfc3339: vm.read_date_rfc3339,
210                region: vm.region,
211                price_per_hour: vm.price_per_hour,
212                price_per_month: vm.price_per_month,
213                aggregated_resource_type: "Vm".to_string(),
214                count: 1,
215            },
216            Resource::Volume(volume) => Aggregate {
217                osc_cost_version: volume.osc_cost_version,
218                account_id: volume.account_id,
219                read_date_rfc3339: volume.read_date_rfc3339,
220                region: volume.region,
221                price_per_hour: volume.price_per_hour,
222                price_per_month: volume.price_per_month,
223                aggregated_resource_type: "Volume".to_string(),
224                count: 1,
225            },
226            Resource::PublicIp(public_ip) => Aggregate {
227                osc_cost_version: public_ip.osc_cost_version,
228                account_id: public_ip.account_id,
229                read_date_rfc3339: public_ip.read_date_rfc3339,
230                region: public_ip.region,
231                price_per_hour: public_ip.price_per_hour,
232                price_per_month: public_ip.price_per_month,
233                aggregated_resource_type: "PublicIp".to_string(),
234                count: 1,
235            },
236            Resource::Snapshot(snapshot) => Aggregate {
237                osc_cost_version: snapshot.osc_cost_version,
238                account_id: snapshot.account_id,
239                read_date_rfc3339: snapshot.read_date_rfc3339,
240                region: snapshot.region,
241                price_per_hour: snapshot.price_per_hour,
242                price_per_month: snapshot.price_per_month,
243                aggregated_resource_type: "Snapshot".to_string(),
244                count: 1,
245            },
246            Resource::NatServices(nat_service) => Aggregate {
247                osc_cost_version: nat_service.osc_cost_version,
248                account_id: nat_service.account_id,
249                read_date_rfc3339: nat_service.read_date_rfc3339,
250                region: nat_service.region,
251                price_per_hour: nat_service.price_per_hour,
252                price_per_month: nat_service.price_per_month,
253                aggregated_resource_type: "NatServices".to_string(),
254                count: 1,
255            },
256            Resource::DedicatedInstance(dedicated_instance) => Aggregate {
257                osc_cost_version: dedicated_instance.osc_cost_version,
258                account_id: dedicated_instance.account_id,
259                read_date_rfc3339: dedicated_instance.read_date_rfc3339,
260                region: dedicated_instance.region,
261                price_per_hour: dedicated_instance.price_per_hour,
262                price_per_month: dedicated_instance.price_per_month,
263                aggregated_resource_type: "DedicatedInstance".to_string(),
264                count: 1,
265            },
266            Resource::Aggregate(aggregate) => aggregate,
267            Resource::FlexibleGpu(flexible_gpu) => Aggregate {
268                osc_cost_version: flexible_gpu.osc_cost_version,
269                account_id: flexible_gpu.account_id,
270                read_date_rfc3339: flexible_gpu.read_date_rfc3339,
271                region: flexible_gpu.region,
272                price_per_hour: flexible_gpu.price_per_hour,
273                price_per_month: flexible_gpu.price_per_month,
274                aggregated_resource_type: "FlexibleGpu".to_string(),
275                count: 1,
276            },
277            Resource::LoadBalancer(load_balancer) => Aggregate {
278                osc_cost_version: load_balancer.osc_cost_version,
279                account_id: load_balancer.account_id,
280                read_date_rfc3339: load_balancer.read_date_rfc3339,
281                region: load_balancer.region,
282                price_per_hour: load_balancer.price_per_hour,
283                price_per_month: load_balancer.price_per_month,
284                aggregated_resource_type: "LoadBalancer".to_string(),
285                count: 1,
286            },
287            Resource::Vpn(resource) => Aggregate {
288                osc_cost_version: resource.osc_cost_version,
289                account_id: resource.account_id,
290                read_date_rfc3339: resource.read_date_rfc3339,
291                region: resource.region,
292                price_per_hour: resource.price_per_hour,
293                price_per_month: resource.price_per_month,
294                aggregated_resource_type: "Vpn".to_string(),
295                count: 1,
296            },
297            Resource::Oos(resource) => Aggregate {
298                osc_cost_version: resource.osc_cost_version,
299                account_id: resource.account_id,
300                read_date_rfc3339: resource.read_date_rfc3339,
301                region: resource.region,
302                price_per_hour: resource.price_per_hour,
303                price_per_month: resource.price_per_month,
304                aggregated_resource_type: "Oos".to_string(),
305                count: 1,
306            },
307        }
308    }
309}