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}