1use crate::config::AwsConfig;
2use anyhow::Result;
3
4#[derive(Clone, Debug, Default)]
6pub struct FsxFileSystem {
7 pub name: String,
9 pub file_system_id: String,
10 pub file_system_arn: String,
11 pub file_system_type: String,
13 pub status: String,
15 pub deployment_type: String,
17 pub storage_class: String,
19 pub storage_capacity: String,
21 pub storage_capacity_gib: i64,
23 pub throughput_capacity: String,
25 pub per_unit_throughput: i64,
27 pub total_throughput_mbps: i64,
29 pub provisioned_iops: String,
31 pub creation_time: String,
32 pub lustre_version: String,
34 pub data_compression_type: String,
36 pub efa: String,
38 pub root_squash: String,
40 pub mount_name: String,
42 pub subnet_ids: String,
44 pub vpc_id: String,
45 pub kms_key_id: String,
46 pub dns_name: String,
48 pub network_interface_ids: String,
50 pub weekly_maintenance_start: String,
52 pub daily_automatic_backup_start: String,
54 pub automatic_backup_retention_days: i32,
56 pub tags: Vec<(String, String)>,
58 pub updates: Vec<FsxUpdate>,
60}
61
62#[derive(Clone, Debug, Default)]
64pub struct FsxUpdate {
65 pub update_type: String,
66 pub target_value: String,
67 pub status: String,
68 pub progress_percent: String,
69 pub request_time: String,
70 pub request_time_raw: String,
72}
73
74#[derive(Clone, Debug, Default)]
76pub struct FsxBackup {
77 pub name: String,
78 pub backup_id: String,
79 pub file_system_type: String,
80 pub lifecycle_state: String,
81 pub progress_percent: String,
82 pub backup_type: String,
83 pub deployment_type: String,
84 pub storage_class: String,
85 pub storage: String,
86 pub resource_id: String,
87 pub resource_name: String,
88 pub backup_time: String,
89 pub active_directory: String,
90 pub lustre_version: String,
91 pub kms_key_id: String,
92}
93
94pub struct FsxClient {
95 config: AwsConfig,
96}
97
98impl FsxClient {
99 pub fn new(config: AwsConfig) -> Self {
100 Self { config }
101 }
102
103 pub async fn list_file_systems(&self) -> Result<Vec<FsxFileSystem>> {
104 let client = self.config.fsx_client();
105
106 let mut file_systems: Vec<FsxFileSystem> = Vec::new();
107 let mut next_token: Option<String> = None;
108
109 loop {
110 let mut request = client.describe_file_systems();
111 if let Some(t) = next_token {
112 request = request.next_token(t);
113 }
114 let response = request.send().await?;
115
116 for fs in response.file_systems() {
117 let file_system_type = fs
118 .file_system_type()
119 .map(|t| format!("{:?}", t))
120 .unwrap_or_default();
121
122 let status = fs
123 .lifecycle()
124 .map(|s| format!("{:?}", s))
125 .unwrap_or_default();
126
127 let storage_class = fs
128 .storage_type()
129 .map(|s| format!("{:?}", s))
130 .unwrap_or_default();
131
132 let storage_capacity_gib = fs.storage_capacity().unwrap_or(0) as i64;
133 let storage_capacity = if storage_capacity_gib > 0 {
134 format!("{} GiB", storage_capacity_gib)
135 } else {
136 String::new()
137 };
138
139 let mut per_unit_throughput: i64 = 0;
143 let mut data_compression_type = String::new();
144 let mut efa = String::new();
145 let mut root_squash = String::new();
146 let mut mount_name = String::new();
147 let (deployment_type, throughput_capacity, provisioned_iops) =
148 if let Some(w) = fs.windows_configuration() {
149 (
150 w.deployment_type().map(|d| format!("{:?}", d)),
151 w.throughput_capacity().map(|t| format!("{} MB/s", t)),
152 None,
153 )
154 } else if let Some(l) = fs.lustre_configuration() {
155 per_unit_throughput = l.per_unit_storage_throughput().unwrap_or(0) as i64;
156 data_compression_type = l
157 .data_compression_type()
158 .map(|d| format!("{:?}", d))
159 .unwrap_or_default();
160 efa = match l.efa_enabled() {
161 Some(true) => "Enabled".to_string(),
162 Some(false) => "Disabled".to_string(),
163 None => String::new(),
164 };
165 root_squash =
166 match l.root_squash_configuration().and_then(|r| r.root_squash()) {
167 Some(rs) if !rs.is_empty() => "Enabled".to_string(),
168 _ => "Disabled".to_string(),
169 };
170 mount_name = l.mount_name().unwrap_or("").to_string();
171 (
172 l.deployment_type().map(|d| format!("{:?}", d)),
173 if per_unit_throughput > 0 {
174 Some(format!("{} MB/s/TiB", per_unit_throughput))
175 } else {
176 None
177 },
178 None,
179 )
180 } else if let Some(o) = fs.ontap_configuration() {
181 (
182 o.deployment_type().map(|d| format!("{:?}", d)),
183 o.throughput_capacity().map(|t| format!("{} MB/s", t)),
184 o.disk_iops_configuration()
185 .and_then(|d| d.iops())
186 .map(|i| i.to_string()),
187 )
188 } else if let Some(z) = fs.open_zfs_configuration() {
189 (
190 z.deployment_type().map(|d| format!("{:?}", d)),
191 z.throughput_capacity().map(|t| format!("{} MB/s", t)),
192 z.disk_iops_configuration()
193 .and_then(|d| d.iops())
194 .map(|i| i.to_string()),
195 )
196 } else {
197 (None, None, None)
198 };
199
200 let total_throughput_mbps = if per_unit_throughput > 0 {
202 per_unit_throughput * storage_capacity_gib / 1000
203 } else {
204 0
205 };
206
207 let (weekly_maintenance_start, daily_automatic_backup_start, retention_days) =
209 if let Some(w) = fs.windows_configuration() {
210 (
211 w.weekly_maintenance_start_time(),
212 w.daily_automatic_backup_start_time(),
213 w.automatic_backup_retention_days(),
214 )
215 } else if let Some(l) = fs.lustre_configuration() {
216 (
217 l.weekly_maintenance_start_time(),
218 l.daily_automatic_backup_start_time(),
219 l.automatic_backup_retention_days(),
220 )
221 } else if let Some(o) = fs.ontap_configuration() {
222 (
223 o.weekly_maintenance_start_time(),
224 o.daily_automatic_backup_start_time(),
225 o.automatic_backup_retention_days(),
226 )
227 } else if let Some(z) = fs.open_zfs_configuration() {
228 (
229 z.weekly_maintenance_start_time(),
230 z.daily_automatic_backup_start_time(),
231 z.automatic_backup_retention_days(),
232 )
233 } else {
234 (None, None, None)
235 };
236 let weekly_maintenance_start = weekly_maintenance_start.unwrap_or("").to_string();
237 let daily_automatic_backup_start =
238 daily_automatic_backup_start.unwrap_or("").to_string();
239 let automatic_backup_retention_days = retention_days.unwrap_or(0);
240
241 let dns_name = fs.dns_name().unwrap_or("").to_string();
242 let network_interface_ids = fs.network_interface_ids().join(", ");
243
244 let mut updates: Vec<FsxUpdate> = fs
246 .administrative_actions()
247 .iter()
248 .map(|a| {
249 let request_time_raw = a
250 .request_time()
251 .and_then(|t| t.fmt(aws_smithy_types::date_time::Format::DateTime).ok())
252 .unwrap_or_default();
253 let target_value = a
254 .target_file_system_values()
255 .map(|t| {
256 if t.storage_capacity().unwrap_or(0) > 0 {
257 format!("{} GiB", t.storage_capacity().unwrap_or(0))
258 } else if let Some(l) = t.lustre_configuration() {
259 l.per_unit_storage_throughput()
260 .map(|v| format!("{} MB/s/TiB", v))
261 .unwrap_or_default()
262 } else if let Some(w) = t.windows_configuration() {
263 w.throughput_capacity()
264 .map(|v| format!("{} MB/s", v))
265 .unwrap_or_default()
266 } else {
267 String::new()
268 }
269 })
270 .unwrap_or_default();
271 FsxUpdate {
272 update_type: a
273 .administrative_action_type()
274 .map(|t| format!("{:?}", t))
275 .unwrap_or_default(),
276 target_value,
277 status: a.status().map(|s| format!("{:?}", s)).unwrap_or_default(),
278 progress_percent: a
279 .progress_percent()
280 .map(|p| format!("{}%", p))
281 .unwrap_or_default(),
282 request_time: request_time_raw.clone(),
283 request_time_raw,
284 }
285 })
286 .collect();
287 updates.sort_by(|a, b| a.request_time_raw.cmp(&b.request_time_raw));
288
289 let creation_time = fs
290 .creation_time()
291 .and_then(|t| t.fmt(aws_smithy_types::date_time::Format::DateTime).ok())
292 .unwrap_or_default();
293
294 let lustre_version =
296 if fs.file_system_type() == Some(&aws_sdk_fsx::types::FileSystemType::Lustre) {
297 fs.file_system_type_version().unwrap_or("").to_string()
298 } else {
299 String::new()
300 };
301
302 let name = fs
303 .tags()
304 .iter()
305 .find(|t| t.key() == Some("Name"))
306 .and_then(|t| t.value())
307 .unwrap_or("")
308 .to_string();
309
310 let subnet_ids = fs.subnet_ids().join(", ");
311
312 let tags: Vec<(String, String)> = fs
313 .tags()
314 .iter()
315 .filter_map(|t| Some((t.key()?.to_string(), t.value()?.to_string())))
316 .collect();
317
318 file_systems.push(FsxFileSystem {
319 name,
320 file_system_id: fs.file_system_id().unwrap_or("").to_string(),
321 file_system_arn: fs.resource_arn().unwrap_or("").to_string(),
322 file_system_type,
323 status,
324 deployment_type: deployment_type.unwrap_or_default(),
325 storage_class,
326 storage_capacity,
327 storage_capacity_gib,
328 throughput_capacity: throughput_capacity.unwrap_or_default(),
329 per_unit_throughput,
330 total_throughput_mbps,
331 provisioned_iops: provisioned_iops.unwrap_or_default(),
332 creation_time,
333 lustre_version,
334 data_compression_type,
335 efa,
336 root_squash,
337 mount_name,
338 subnet_ids,
339 vpc_id: fs.vpc_id().unwrap_or("").to_string(),
340 kms_key_id: fs.kms_key_id().unwrap_or("").to_string(),
341 dns_name,
342 network_interface_ids,
343 weekly_maintenance_start,
344 daily_automatic_backup_start,
345 automatic_backup_retention_days,
346 tags,
347 updates,
348 });
349 }
350
351 next_token = response.next_token().map(|s| s.to_string());
352 if next_token.is_none() {
353 break;
354 }
355 }
356
357 file_systems.sort_by(|a, b| b.creation_time.cmp(&a.creation_time));
359
360 Ok(file_systems)
361 }
362
363 pub async fn describe_backups(&self, file_system_id: &str) -> Result<Vec<FsxBackup>> {
365 use aws_sdk_fsx::types::Filter;
366 let client = self.config.fsx_client();
367
368 let mut out: Vec<FsxBackup> = Vec::new();
369 let mut next_token: Option<String> = None;
370
371 loop {
372 let mut req = client.describe_backups().filters(
373 Filter::builder()
374 .name(aws_sdk_fsx::types::FilterName::FileSystemId)
375 .values(file_system_id)
376 .build(),
377 );
378 if let Some(t) = next_token {
379 req = req.next_token(t);
380 }
381 let resp = req.send().await?;
382
383 for b in resp.backups() {
384 let name = b
385 .tags()
386 .iter()
387 .find(|t| t.key() == Some("Name"))
388 .and_then(|t| t.value())
389 .unwrap_or("")
390 .to_string();
391
392 let fs = b.file_system();
393 let file_system_type = fs
394 .and_then(|f| f.file_system_type())
395 .map(|t| format!("{:?}", t))
396 .unwrap_or_default();
397 let deployment_type = fs.map(deployment_type_of).unwrap_or_default();
398 let storage_class = fs
399 .and_then(|f| f.storage_type())
400 .map(|s| format!("{:?}", s))
401 .unwrap_or_default();
402 let storage = fs
403 .and_then(|f| f.storage_capacity())
404 .map(|c| format!("{} GiB", c))
405 .unwrap_or_default();
406 let resource_id = fs
407 .and_then(|f| f.file_system_id())
408 .unwrap_or("")
409 .to_string();
410 let resource_name = fs
411 .map(|f| {
412 f.tags()
413 .iter()
414 .find(|t| t.key() == Some("Name"))
415 .and_then(|t| t.value())
416 .unwrap_or("")
417 .to_string()
418 })
419 .unwrap_or_default();
420 let lustre_version = fs
421 .filter(|f| {
422 f.file_system_type() == Some(&aws_sdk_fsx::types::FileSystemType::Lustre)
423 })
424 .and_then(|f| f.file_system_type_version())
425 .unwrap_or("")
426 .to_string();
427 let active_directory = b
428 .directory_information()
429 .and_then(|d| d.active_directory_id())
430 .unwrap_or("")
431 .to_string();
432
433 out.push(FsxBackup {
434 name,
435 backup_id: b.backup_id().unwrap_or("").to_string(),
436 file_system_type,
437 lifecycle_state: b
438 .lifecycle()
439 .map(|s| format!("{:?}", s))
440 .unwrap_or_default(),
441 progress_percent: b
442 .progress_percent()
443 .map(|p| format!("{}%", p))
444 .unwrap_or_default(),
445 backup_type: b.r#type().map(|t| format!("{:?}", t)).unwrap_or_default(),
446 deployment_type,
447 storage_class,
448 storage,
449 resource_id,
450 resource_name,
451 backup_time: b
452 .creation_time()
453 .and_then(|t| t.fmt(aws_smithy_types::date_time::Format::DateTime).ok())
454 .unwrap_or_default(),
455 active_directory,
456 lustre_version,
457 kms_key_id: b.kms_key_id().unwrap_or("").to_string(),
458 });
459 }
460
461 next_token = resp.next_token().map(|s| s.to_string());
462 if next_token.is_none() {
463 break;
464 }
465 }
466
467 out.sort_by(|a, b| b.backup_time.cmp(&a.backup_time));
469 Ok(out)
470 }
471}
472
473fn deployment_type_of(fs: &aws_sdk_fsx::types::FileSystem) -> String {
475 if let Some(w) = fs.windows_configuration() {
476 w.deployment_type()
477 .map(|d| format!("{:?}", d))
478 .unwrap_or_default()
479 } else if let Some(l) = fs.lustre_configuration() {
480 l.deployment_type()
481 .map(|d| format!("{:?}", d))
482 .unwrap_or_default()
483 } else if let Some(o) = fs.ontap_configuration() {
484 o.deployment_type()
485 .map(|d| format!("{:?}", d))
486 .unwrap_or_default()
487 } else if let Some(z) = fs.open_zfs_configuration() {
488 z.deployment_type()
489 .map(|d| format!("{:?}", d))
490 .unwrap_or_default()
491 } else {
492 String::new()
493 }
494}