1use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub enum TierType {
13 #[serde(rename = "s3")]
14 S3,
15 #[serde(rename = "rustfs")]
16 RustFS,
17 #[serde(rename = "minio")]
18 MinIO,
19 #[serde(rename = "aliyun")]
20 Aliyun,
21 #[serde(rename = "tencent")]
22 Tencent,
23 #[serde(rename = "huaweicloud")]
24 Huaweicloud,
25 #[serde(rename = "azure")]
26 Azure,
27 #[serde(rename = "gcs")]
28 GCS,
29 #[serde(rename = "r2")]
30 R2,
31}
32
33impl fmt::Display for TierType {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 match self {
36 TierType::S3 => write!(f, "S3"),
37 TierType::RustFS => write!(f, "RustFS"),
38 TierType::MinIO => write!(f, "MinIO"),
39 TierType::Aliyun => write!(f, "Aliyun"),
40 TierType::Tencent => write!(f, "Tencent"),
41 TierType::Huaweicloud => write!(f, "Huaweicloud"),
42 TierType::Azure => write!(f, "Azure"),
43 TierType::GCS => write!(f, "GCS"),
44 TierType::R2 => write!(f, "R2"),
45 }
46 }
47}
48
49impl std::str::FromStr for TierType {
50 type Err = String;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 match s.to_lowercase().as_str() {
54 "s3" => Ok(TierType::S3),
55 "rustfs" => Ok(TierType::RustFS),
56 "minio" => Ok(TierType::MinIO),
57 "aliyun" => Ok(TierType::Aliyun),
58 "tencent" => Ok(TierType::Tencent),
59 "huaweicloud" => Ok(TierType::Huaweicloud),
60 "azure" => Ok(TierType::Azure),
61 "gcs" => Ok(TierType::GCS),
62 "r2" => Ok(TierType::R2),
63 _ => Err(format!(
64 "Invalid tier type: {s}. Valid types: s3, rustfs, minio, aliyun, tencent, huaweicloud, azure, gcs, r2"
65 )),
66 }
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(default)]
77pub struct TierConfig {
78 #[serde(rename = "type")]
79 pub tier_type: TierType,
80
81 #[serde(skip)]
84 pub name: String,
85
86 #[serde(rename = "s3", skip_serializing_if = "Option::is_none")]
87 pub s3: Option<TierS3>,
88 #[serde(rename = "rustfs", skip_serializing_if = "Option::is_none")]
89 pub rustfs: Option<TierRustFS>,
90 #[serde(rename = "minio", skip_serializing_if = "Option::is_none")]
91 pub minio: Option<TierMinIO>,
92 #[serde(rename = "aliyun", skip_serializing_if = "Option::is_none")]
93 pub aliyun: Option<TierAliyun>,
94 #[serde(rename = "tencent", skip_serializing_if = "Option::is_none")]
95 pub tencent: Option<TierTencent>,
96 #[serde(rename = "huaweicloud", skip_serializing_if = "Option::is_none")]
97 pub huaweicloud: Option<TierHuaweicloud>,
98 #[serde(rename = "azure", skip_serializing_if = "Option::is_none")]
99 pub azure: Option<TierAzure>,
100 #[serde(rename = "gcs", skip_serializing_if = "Option::is_none")]
101 pub gcs: Option<TierGCS>,
102 #[serde(rename = "r2", skip_serializing_if = "Option::is_none")]
103 pub r2: Option<TierR2>,
104}
105
106impl Default for TierConfig {
107 fn default() -> Self {
108 Self {
109 tier_type: TierType::S3,
110 name: String::new(),
111 s3: None,
112 rustfs: None,
113 minio: None,
114 aliyun: None,
115 tencent: None,
116 huaweicloud: None,
117 azure: None,
118 gcs: None,
119 r2: None,
120 }
121 }
122}
123
124impl TierConfig {
125 pub fn tier_name(&self) -> &str {
127 if !self.name.is_empty() {
128 return &self.name;
129 }
130 match self.tier_type {
131 TierType::S3 => self.s3.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
132 TierType::RustFS => self.rustfs.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
133 TierType::MinIO => self.minio.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
134 TierType::Aliyun => self.aliyun.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
135 TierType::Tencent => self.tencent.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
136 TierType::Huaweicloud => self
137 .huaweicloud
138 .as_ref()
139 .map(|c| c.name.as_str())
140 .unwrap_or(""),
141 TierType::Azure => self.azure.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
142 TierType::GCS => self.gcs.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
143 TierType::R2 => self.r2.as_ref().map(|c| c.name.as_str()).unwrap_or(""),
144 }
145 }
146
147 pub fn endpoint(&self) -> &str {
149 match self.tier_type {
150 TierType::S3 => self.s3.as_ref().map(|c| c.endpoint.as_str()).unwrap_or(""),
151 TierType::RustFS => self
152 .rustfs
153 .as_ref()
154 .map(|c| c.endpoint.as_str())
155 .unwrap_or(""),
156 TierType::MinIO => self
157 .minio
158 .as_ref()
159 .map(|c| c.endpoint.as_str())
160 .unwrap_or(""),
161 TierType::Aliyun => self
162 .aliyun
163 .as_ref()
164 .map(|c| c.endpoint.as_str())
165 .unwrap_or(""),
166 TierType::Tencent => self
167 .tencent
168 .as_ref()
169 .map(|c| c.endpoint.as_str())
170 .unwrap_or(""),
171 TierType::Huaweicloud => self
172 .huaweicloud
173 .as_ref()
174 .map(|c| c.endpoint.as_str())
175 .unwrap_or(""),
176 TierType::Azure => self
177 .azure
178 .as_ref()
179 .map(|c| c.endpoint.as_str())
180 .unwrap_or(""),
181 TierType::GCS => self.gcs.as_ref().map(|c| c.endpoint.as_str()).unwrap_or(""),
182 TierType::R2 => self.r2.as_ref().map(|c| c.endpoint.as_str()).unwrap_or(""),
183 }
184 }
185
186 pub fn bucket(&self) -> &str {
188 match self.tier_type {
189 TierType::S3 => self.s3.as_ref().map(|c| c.bucket.as_str()).unwrap_or(""),
190 TierType::RustFS => self
191 .rustfs
192 .as_ref()
193 .map(|c| c.bucket.as_str())
194 .unwrap_or(""),
195 TierType::MinIO => self.minio.as_ref().map(|c| c.bucket.as_str()).unwrap_or(""),
196 TierType::Aliyun => self
197 .aliyun
198 .as_ref()
199 .map(|c| c.bucket.as_str())
200 .unwrap_or(""),
201 TierType::Tencent => self
202 .tencent
203 .as_ref()
204 .map(|c| c.bucket.as_str())
205 .unwrap_or(""),
206 TierType::Huaweicloud => self
207 .huaweicloud
208 .as_ref()
209 .map(|c| c.bucket.as_str())
210 .unwrap_or(""),
211 TierType::Azure => self.azure.as_ref().map(|c| c.bucket.as_str()).unwrap_or(""),
212 TierType::GCS => self.gcs.as_ref().map(|c| c.bucket.as_str()).unwrap_or(""),
213 TierType::R2 => self.r2.as_ref().map(|c| c.bucket.as_str()).unwrap_or(""),
214 }
215 }
216
217 pub fn prefix(&self) -> &str {
219 match self.tier_type {
220 TierType::S3 => self.s3.as_ref().map(|c| c.prefix.as_str()).unwrap_or(""),
221 TierType::RustFS => self
222 .rustfs
223 .as_ref()
224 .map(|c| c.prefix.as_str())
225 .unwrap_or(""),
226 TierType::MinIO => self.minio.as_ref().map(|c| c.prefix.as_str()).unwrap_or(""),
227 TierType::Aliyun => self
228 .aliyun
229 .as_ref()
230 .map(|c| c.prefix.as_str())
231 .unwrap_or(""),
232 TierType::Tencent => self
233 .tencent
234 .as_ref()
235 .map(|c| c.prefix.as_str())
236 .unwrap_or(""),
237 TierType::Huaweicloud => self
238 .huaweicloud
239 .as_ref()
240 .map(|c| c.prefix.as_str())
241 .unwrap_or(""),
242 TierType::Azure => self.azure.as_ref().map(|c| c.prefix.as_str()).unwrap_or(""),
243 TierType::GCS => self.gcs.as_ref().map(|c| c.prefix.as_str()).unwrap_or(""),
244 TierType::R2 => self.r2.as_ref().map(|c| c.prefix.as_str()).unwrap_or(""),
245 }
246 }
247
248 pub fn region(&self) -> &str {
250 match self.tier_type {
251 TierType::S3 => self.s3.as_ref().map(|c| c.region.as_str()).unwrap_or(""),
252 TierType::RustFS => self
253 .rustfs
254 .as_ref()
255 .map(|c| c.region.as_str())
256 .unwrap_or(""),
257 TierType::MinIO => self.minio.as_ref().map(|c| c.region.as_str()).unwrap_or(""),
258 TierType::Aliyun => self
259 .aliyun
260 .as_ref()
261 .map(|c| c.region.as_str())
262 .unwrap_or(""),
263 TierType::Tencent => self
264 .tencent
265 .as_ref()
266 .map(|c| c.region.as_str())
267 .unwrap_or(""),
268 TierType::Huaweicloud => self
269 .huaweicloud
270 .as_ref()
271 .map(|c| c.region.as_str())
272 .unwrap_or(""),
273 TierType::Azure => self.azure.as_ref().map(|c| c.region.as_str()).unwrap_or(""),
274 TierType::GCS => self.gcs.as_ref().map(|c| c.region.as_str()).unwrap_or(""),
275 TierType::R2 => self.r2.as_ref().map(|c| c.region.as_str()).unwrap_or(""),
276 }
277 }
278}
279
280#[derive(Debug, Clone, Default, Serialize, Deserialize)]
282#[serde(default)]
283pub struct TierCreds {
284 #[serde(rename = "accessKey")]
285 pub access_key: String,
286 #[serde(rename = "secretKey")]
287 pub secret_key: String,
288}
289
290#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
292pub struct ManualTransitionRunRequest {
293 pub bucket: String,
294 pub prefix: String,
295 pub tier: Option<String>,
296 pub dry_run: bool,
297 pub max_objects: u64,
298 pub max_duration_seconds: Option<u64>,
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303pub struct ManualTransitionRunResponse {
304 pub state: String,
305 pub mode: String,
306 #[serde(default)]
307 pub job_id: Option<String>,
308 #[serde(default)]
309 pub status_endpoint: Option<String>,
310 #[serde(default)]
311 pub cancel_endpoint: Option<String>,
312 pub report: ManualTransitionRunReport,
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
317pub struct ManualTransitionJobResponse {
318 pub status: String,
319 pub mode: String,
320 pub job_id: String,
321 pub status_endpoint: String,
322 pub cancel_endpoint: String,
323 pub cancel_requested: bool,
324 pub bucket: String,
325 pub prefix: String,
326 pub tier: Option<String>,
327 pub dry_run: bool,
328 pub created_at_unix_nanos: i128,
329 pub updated_at_unix_nanos: i128,
330 pub completed_at_unix_nanos: Option<i128>,
331 pub report: ManualTransitionRunReport,
332 #[serde(default)]
333 pub queue_snapshot: ManualTransitionQueueSnapshot,
334 pub failure_reason: Option<String>,
335}
336
337#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
339pub struct ManualTransitionQueueSnapshot {
340 #[serde(default)]
341 pub queue_capacity: u64,
342 #[serde(default)]
343 pub queued: u64,
344 #[serde(default)]
345 pub active: u64,
346 #[serde(default)]
347 pub workers: u64,
348 #[serde(default)]
349 pub queue_full: u64,
350 #[serde(default)]
351 pub queue_send_timeout: u64,
352 #[serde(default)]
353 pub compensation_pending: u64,
354 #[serde(default)]
355 pub compensation_running: u64,
356}
357
358#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
360pub struct ManualTransitionRunReport {
361 pub bucket: String,
362 pub prefix: String,
363 pub tier: Option<String>,
364 pub dry_run: bool,
365 pub lifecycle_config_found: bool,
366 pub scanned: u64,
367 pub eligible: u64,
368 pub enqueued: u64,
369 pub dry_run_eligible: u64,
370 pub skipped_not_transition: u64,
371 pub skipped_tier: u64,
372 pub skipped_delete_marker: u64,
373 pub skipped_directory: u64,
374 pub skipped_replication: u64,
375 #[serde(default)]
376 pub skipped_already_transitioned: u64,
377 pub skipped_already_in_flight: u64,
378 pub skipped_queue_full: u64,
379 pub skipped_queue_closed: u64,
380 pub skipped_queue_timeout: u64,
381 #[serde(default)]
382 pub transition_completed: u64,
383 #[serde(default)]
384 pub transition_failed: u64,
385 #[serde(default)]
386 pub tier_failure: u64,
387 pub truncated_by_limit: bool,
388 #[serde(default)]
389 pub truncated_by_duration: bool,
390 #[serde(default)]
391 pub cancelled: bool,
392 #[serde(default)]
393 pub continuation_token: Option<String>,
394}
395
396#[derive(Debug, Clone, Serialize, Deserialize, Default)]
400#[serde(default)]
401pub struct TierS3 {
402 pub name: String,
403 pub endpoint: String,
404 #[serde(rename = "accessKey")]
405 pub access_key: String,
406 #[serde(rename = "secretKey")]
407 pub secret_key: String,
408 pub bucket: String,
409 pub prefix: String,
410 pub region: String,
411 #[serde(rename = "storageClass")]
412 pub storage_class: String,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize, Default)]
416#[serde(default)]
417pub struct TierRustFS {
418 pub name: String,
419 pub endpoint: String,
420 #[serde(rename = "accessKey")]
421 pub access_key: String,
422 #[serde(rename = "secretKey")]
423 pub secret_key: String,
424 pub bucket: String,
425 pub prefix: String,
426 pub region: String,
427 #[serde(rename = "storageClass")]
428 pub storage_class: String,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, Default)]
432#[serde(default)]
433pub struct TierMinIO {
434 pub name: String,
435 pub endpoint: String,
436 #[serde(rename = "accessKey")]
437 pub access_key: String,
438 #[serde(rename = "secretKey")]
439 pub secret_key: String,
440 pub bucket: String,
441 pub prefix: String,
442 pub region: String,
443}
444
445#[derive(Debug, Clone, Serialize, Deserialize, Default)]
446#[serde(default)]
447pub struct TierAliyun {
448 pub name: String,
449 pub endpoint: String,
450 #[serde(rename = "accessKey")]
451 pub access_key: String,
452 #[serde(rename = "secretKey")]
453 pub secret_key: String,
454 pub bucket: String,
455 pub prefix: String,
456 pub region: String,
457}
458
459#[derive(Debug, Clone, Serialize, Deserialize, Default)]
460#[serde(default)]
461pub struct TierTencent {
462 pub name: String,
463 pub endpoint: String,
464 #[serde(rename = "accessKey")]
465 pub access_key: String,
466 #[serde(rename = "secretKey")]
467 pub secret_key: String,
468 pub bucket: String,
469 pub prefix: String,
470 pub region: String,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize, Default)]
474#[serde(default)]
475pub struct TierHuaweicloud {
476 pub name: String,
477 pub endpoint: String,
478 #[serde(rename = "accessKey")]
479 pub access_key: String,
480 #[serde(rename = "secretKey")]
481 pub secret_key: String,
482 pub bucket: String,
483 pub prefix: String,
484 pub region: String,
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize, Default)]
488#[serde(default)]
489pub struct TierAzure {
490 pub name: String,
491 pub endpoint: String,
492 #[serde(rename = "accessKey")]
493 pub access_key: String,
494 #[serde(rename = "secretKey")]
495 pub secret_key: String,
496 pub bucket: String,
497 pub prefix: String,
498 pub region: String,
499 #[serde(rename = "storageClass")]
500 pub storage_class: String,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize, Default)]
504#[serde(default)]
505pub struct TierGCS {
506 pub name: String,
507 pub endpoint: String,
508 #[serde(rename = "creds")]
509 pub creds: String,
510 pub bucket: String,
511 pub prefix: String,
512 pub region: String,
513 #[serde(rename = "storageClass")]
514 pub storage_class: String,
515}
516
517#[derive(Debug, Clone, Serialize, Deserialize, Default)]
518#[serde(default)]
519pub struct TierR2 {
520 pub name: String,
521 pub endpoint: String,
522 #[serde(rename = "accessKey")]
523 pub access_key: String,
524 #[serde(rename = "secretKey")]
525 pub secret_key: String,
526 pub bucket: String,
527 pub prefix: String,
528 pub region: String,
529}
530
531#[cfg(test)]
532mod tests {
533 use super::*;
534
535 #[test]
536 fn test_tier_type_display() {
537 assert_eq!(TierType::S3.to_string(), "S3");
538 assert_eq!(TierType::RustFS.to_string(), "RustFS");
539 assert_eq!(TierType::MinIO.to_string(), "MinIO");
540 assert_eq!(TierType::Azure.to_string(), "Azure");
541 assert_eq!(TierType::GCS.to_string(), "GCS");
542 assert_eq!(TierType::R2.to_string(), "R2");
543 }
544
545 #[test]
546 fn test_tier_type_from_str() {
547 assert_eq!("s3".parse::<TierType>().unwrap(), TierType::S3);
548 assert_eq!("rustfs".parse::<TierType>().unwrap(), TierType::RustFS);
549 assert_eq!("MINIO".parse::<TierType>().unwrap(), TierType::MinIO);
550 assert_eq!("Azure".parse::<TierType>().unwrap(), TierType::Azure);
551 assert!("invalid".parse::<TierType>().is_err());
552 }
553
554 #[test]
555 fn test_tier_config_serialization_s3() {
556 let config = TierConfig {
557 tier_type: TierType::S3,
558 name: "WARM".to_string(),
559 s3: Some(TierS3 {
560 name: "WARM".to_string(),
561 endpoint: "https://s3.amazonaws.com".to_string(),
562 access_key: "AKID".to_string(),
563 secret_key: "REDACTED".to_string(),
564 bucket: "warm-bucket".to_string(),
565 prefix: "tier/".to_string(),
566 region: "us-east-1".to_string(),
567 storage_class: "STANDARD_IA".to_string(),
568 }),
569 ..Default::default()
570 };
571
572 let json = serde_json::to_string(&config).unwrap();
573 assert!(json.contains(r#""type":"s3""#));
574 assert!(json.contains("warm-bucket"));
575
576 let decoded: TierConfig = serde_json::from_str(&json).unwrap();
577 assert_eq!(decoded.tier_type, TierType::S3);
578 assert_eq!(decoded.tier_name(), "WARM");
579 assert_eq!(decoded.bucket(), "warm-bucket");
580 }
581
582 #[test]
583 fn test_tier_config_deserialization_from_backend() {
584 let json = r#"{"type":"rustfs","rustfs":{"name":"ARCHIVE","endpoint":"http://remote:9000","accessKey":"admin","secretKey":"REDACTED","bucket":"archive","prefix":"","region":""}}"#;
586 let config: TierConfig = serde_json::from_str(json).unwrap();
587 assert_eq!(config.tier_type, TierType::RustFS);
588 assert_eq!(config.tier_name(), "ARCHIVE");
589 assert_eq!(config.endpoint(), "http://remote:9000");
590 assert_eq!(config.bucket(), "archive");
591 }
592
593 #[test]
594 fn test_tier_creds_serialization() {
595 let creds = TierCreds {
596 access_key: "newkey".to_string(),
597 secret_key: "newsecret".to_string(),
598 };
599
600 let json = serde_json::to_string(&creds).unwrap();
601 assert!(json.contains("accessKey"));
602 assert!(json.contains("secretKey"));
603
604 let decoded: TierCreds = serde_json::from_str(&json).unwrap();
605 assert_eq!(decoded.access_key, "newkey");
606 }
607}