1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3use std::sync::Arc;
4
5use chrono::{DateTime, Utc};
6use fakecloud_aws::arn::Arn;
7use parking_lot::RwLock;
8use uuid::Uuid;
9
10pub type SharedRdsState = Arc<RwLock<fakecloud_core::multi_account::MultiAccountState<RdsState>>>;
11
12impl fakecloud_core::multi_account::AccountState for RdsState {
13 fn new_for_account(account_id: &str, region: &str, _endpoint: &str) -> Self {
14 Self::new(account_id, region)
15 }
16}
17
18pub const SUPPORTED_INSTANCE_CLASSES: &[&str] = &[
20 "db.t3.micro",
21 "db.t3.small",
22 "db.t3.medium",
23 "db.t3.large",
24 "db.t4g.micro",
25 "db.t4g.small",
26 "db.m5.large",
27];
28
29#[derive(Clone, serde::Serialize, serde::Deserialize)]
30pub struct DbInstance {
31 pub db_instance_identifier: String,
32 pub db_instance_arn: String,
33 pub db_instance_class: String,
34 pub engine: String,
35 pub engine_version: String,
36 pub db_instance_status: String,
37 pub master_username: String,
38 pub db_name: Option<String>,
39 pub endpoint_address: String,
40 pub port: i32,
41 pub allocated_storage: i32,
42 pub publicly_accessible: bool,
43 pub deletion_protection: bool,
44 pub created_at: DateTime<Utc>,
45 pub dbi_resource_id: String,
46 pub master_user_password: String,
47 pub container_id: String,
48 pub host_port: u16,
49 pub tags: Vec<RdsTag>,
50 pub read_replica_source_db_instance_identifier: Option<String>,
51 pub read_replica_db_instance_identifiers: Vec<String>,
52 pub vpc_security_group_ids: Vec<String>,
53 pub db_parameter_group_name: Option<String>,
54 pub backup_retention_period: i32,
55 pub preferred_backup_window: String,
56 #[serde(default)]
57 pub preferred_maintenance_window: Option<String>,
58 pub latest_restorable_time: Option<DateTime<Utc>>,
59 pub option_group_name: Option<String>,
60 pub multi_az: bool,
61 pub pending_modified_values: Option<PendingModifiedValues>,
62 #[serde(default)]
65 pub availability_zone: Option<String>,
66 #[serde(default)]
67 pub storage_type: Option<String>,
68 #[serde(default)]
69 pub storage_encrypted: bool,
70 #[serde(default)]
71 pub kms_key_id: Option<String>,
72 #[serde(default)]
73 pub iam_database_authentication_enabled: bool,
74 #[serde(default)]
75 pub iops: Option<i32>,
76 #[serde(default)]
77 pub monitoring_interval: Option<i32>,
78 #[serde(default)]
79 pub monitoring_role_arn: Option<String>,
80 #[serde(default)]
81 pub performance_insights_enabled: bool,
82 #[serde(default)]
83 pub performance_insights_kms_key_id: Option<String>,
84 #[serde(default)]
85 pub performance_insights_retention_period: Option<i32>,
86 #[serde(default)]
87 pub enabled_cloudwatch_logs_exports: Vec<String>,
88 #[serde(default)]
89 pub ca_certificate_identifier: Option<String>,
90 #[serde(default)]
91 pub network_type: Option<String>,
92 #[serde(default)]
93 pub character_set_name: Option<String>,
94 #[serde(default)]
95 pub auto_minor_version_upgrade: Option<bool>,
96 #[serde(default)]
97 pub copy_tags_to_snapshot: Option<bool>,
98 #[serde(default)]
99 pub master_user_secret_arn: Option<String>,
100 #[serde(default)]
101 pub master_user_secret_kms_key_id: Option<String>,
102 #[serde(default)]
106 pub license_model: Option<String>,
107 #[serde(default)]
108 pub max_allocated_storage: Option<i32>,
109 #[serde(default)]
110 pub multi_tenant: Option<bool>,
111 #[serde(default)]
112 pub storage_throughput: Option<i32>,
113 #[serde(default)]
114 pub tde_credential_arn: Option<String>,
115 #[serde(default)]
116 pub delete_automated_backups: Option<bool>,
117 #[serde(default)]
118 pub db_security_groups: Vec<String>,
119 #[serde(default)]
122 pub domain: Option<String>,
123 #[serde(default)]
124 pub domain_fqdn: Option<String>,
125 #[serde(default)]
126 pub domain_ou: Option<String>,
127 #[serde(default)]
128 pub domain_iam_role_name: Option<String>,
129 #[serde(default)]
130 pub domain_auth_secret_arn: Option<String>,
131 #[serde(default)]
132 pub domain_dns_ips: Vec<String>,
133 #[serde(default)]
137 pub db_cluster_identifier: Option<String>,
138 #[serde(default)]
143 pub activity_stream: Option<ActivityStreamConfig>,
144}
145
146#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
151pub struct ActivityStreamConfig {
152 pub status: String,
154 #[serde(default)]
155 pub mode: Option<String>,
156 #[serde(default)]
157 pub kms_key_id: Option<String>,
158 #[serde(default)]
159 pub kinesis_stream_name: Option<String>,
160}
161
162#[derive(Clone, Default, serde::Serialize, serde::Deserialize)]
163pub struct PendingModifiedValues {
164 pub db_instance_class: Option<String>,
165 pub allocated_storage: Option<i32>,
166 pub backup_retention_period: Option<i32>,
167 pub multi_az: Option<bool>,
168 pub engine_version: Option<String>,
169 pub master_user_password: Option<String>,
170 #[serde(default)]
171 pub preferred_backup_window: Option<String>,
172 #[serde(default)]
173 pub preferred_maintenance_window: Option<String>,
174 #[serde(default)]
175 pub db_parameter_group_name: Option<String>,
176 #[serde(default)]
177 pub iops: Option<i32>,
178 #[serde(default)]
179 pub storage_type: Option<String>,
180 #[serde(default)]
181 pub monitoring_interval: Option<i32>,
182 #[serde(default)]
183 pub performance_insights_enabled: Option<bool>,
184 #[serde(default)]
185 pub enabled_cloudwatch_logs_exports: Option<Vec<String>>,
186 #[serde(default)]
187 pub storage_throughput: Option<i32>,
188 #[serde(default)]
189 pub license_model: Option<String>,
190 #[serde(default)]
191 pub multi_tenant: Option<bool>,
192 #[serde(default)]
193 pub publicly_accessible: Option<bool>,
194 #[serde(default)]
195 pub tde_credential_arn: Option<String>,
196 #[serde(default)]
197 pub port: Option<i32>,
198 #[serde(default)]
199 pub ca_certificate_identifier: Option<String>,
200}
201
202impl fmt::Debug for PendingModifiedValues {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 f.debug_struct("PendingModifiedValues")
205 .field("db_instance_class", &self.db_instance_class)
206 .field("allocated_storage", &self.allocated_storage)
207 .field("backup_retention_period", &self.backup_retention_period)
208 .field("multi_az", &self.multi_az)
209 .field("engine_version", &self.engine_version)
210 .field(
211 "master_user_password",
212 &self.master_user_password.as_ref().map(|_| "<redacted>"),
213 )
214 .field("preferred_backup_window", &self.preferred_backup_window)
215 .field(
216 "preferred_maintenance_window",
217 &self.preferred_maintenance_window,
218 )
219 .field("db_parameter_group_name", &self.db_parameter_group_name)
220 .field("iops", &self.iops)
221 .field("storage_type", &self.storage_type)
222 .field("monitoring_interval", &self.monitoring_interval)
223 .field(
224 "performance_insights_enabled",
225 &self.performance_insights_enabled,
226 )
227 .field(
228 "enabled_cloudwatch_logs_exports",
229 &self.enabled_cloudwatch_logs_exports,
230 )
231 .field("storage_throughput", &self.storage_throughput)
232 .field("license_model", &self.license_model)
233 .field("multi_tenant", &self.multi_tenant)
234 .field("publicly_accessible", &self.publicly_accessible)
235 .field("tde_credential_arn", &self.tde_credential_arn)
236 .field("port", &self.port)
237 .field("ca_certificate_identifier", &self.ca_certificate_identifier)
238 .finish()
239 }
240}
241
242impl fmt::Debug for DbInstance {
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 f.debug_struct("DbInstance")
245 .field("db_instance_identifier", &self.db_instance_identifier)
246 .field("db_instance_arn", &self.db_instance_arn)
247 .field("db_instance_class", &self.db_instance_class)
248 .field("engine", &self.engine)
249 .field("engine_version", &self.engine_version)
250 .field("db_instance_status", &self.db_instance_status)
251 .field("master_username", &self.master_username)
252 .field("db_name", &self.db_name)
253 .field("endpoint_address", &self.endpoint_address)
254 .field("port", &self.port)
255 .field("allocated_storage", &self.allocated_storage)
256 .field("publicly_accessible", &self.publicly_accessible)
257 .field("deletion_protection", &self.deletion_protection)
258 .field("created_at", &self.created_at)
259 .field("dbi_resource_id", &self.dbi_resource_id)
260 .field("master_user_password", &"<redacted>")
261 .field("container_id", &self.container_id)
262 .field("host_port", &self.host_port)
263 .field("tags", &self.tags)
264 .field(
265 "read_replica_source_db_instance_identifier",
266 &self.read_replica_source_db_instance_identifier,
267 )
268 .field(
269 "read_replica_db_instance_identifiers",
270 &self.read_replica_db_instance_identifiers,
271 )
272 .field("vpc_security_group_ids", &self.vpc_security_group_ids)
273 .field("db_parameter_group_name", &self.db_parameter_group_name)
274 .field("backup_retention_period", &self.backup_retention_period)
275 .field("preferred_backup_window", &self.preferred_backup_window)
276 .field("latest_restorable_time", &self.latest_restorable_time)
277 .field("option_group_name", &self.option_group_name)
278 .field("multi_az", &self.multi_az)
279 .field("pending_modified_values", &self.pending_modified_values)
280 .finish()
281 }
282}
283
284#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
285pub struct RdsTag {
286 pub key: String,
287 pub value: String,
288}
289
290#[derive(Clone, serde::Serialize, serde::Deserialize)]
291pub struct DbSnapshot {
292 pub db_snapshot_identifier: String,
293 pub db_snapshot_arn: String,
294 pub db_instance_identifier: String,
295 pub snapshot_create_time: DateTime<Utc>,
296 pub engine: String,
297 pub engine_version: String,
298 pub allocated_storage: i32,
299 pub status: String,
300 pub port: i32,
301 pub master_username: String,
302 pub db_name: Option<String>,
303 pub dbi_resource_id: String,
304 pub snapshot_type: String,
305 pub master_user_password: String,
306 pub tags: Vec<RdsTag>,
307 pub dump_data: Vec<u8>,
308 #[serde(default)]
309 pub availability_zone: Option<String>,
310 #[serde(default)]
311 pub vpc_id: Option<String>,
312 #[serde(default)]
313 pub instance_create_time: Option<DateTime<Utc>>,
314 #[serde(default)]
315 pub license_model: Option<String>,
316 #[serde(default)]
317 pub iops: Option<i32>,
318 #[serde(default)]
319 pub option_group_name: Option<String>,
320 #[serde(default)]
321 pub percent_progress: Option<i32>,
322 #[serde(default)]
323 pub storage_type: Option<String>,
324 #[serde(default)]
325 pub encrypted: bool,
326 #[serde(default)]
327 pub kms_key_id: Option<String>,
328 #[serde(default)]
329 pub iam_database_authentication_enabled: bool,
330 #[serde(default)]
331 pub timezone: Option<String>,
332 #[serde(default)]
333 pub storage_throughput: Option<i32>,
334 #[serde(default)]
340 pub snapshot_attributes: BTreeMap<String, Vec<String>>,
341}
342
343impl fmt::Debug for DbSnapshot {
344 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345 f.debug_struct("DbSnapshot")
346 .field("db_snapshot_identifier", &self.db_snapshot_identifier)
347 .field("db_snapshot_arn", &self.db_snapshot_arn)
348 .field("db_instance_identifier", &self.db_instance_identifier)
349 .field("snapshot_create_time", &self.snapshot_create_time)
350 .field("engine", &self.engine)
351 .field("engine_version", &self.engine_version)
352 .field("allocated_storage", &self.allocated_storage)
353 .field("status", &self.status)
354 .field("port", &self.port)
355 .field("master_username", &self.master_username)
356 .field("db_name", &self.db_name)
357 .field("dbi_resource_id", &self.dbi_resource_id)
358 .field("snapshot_type", &self.snapshot_type)
359 .field("master_user_password", &"<redacted>")
360 .field("tags", &self.tags)
361 .field("dump_data", &format!("<{} bytes>", self.dump_data.len()))
362 .finish()
363 }
364}
365
366#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
367pub struct RdsState {
368 pub account_id: String,
369 pub region: String,
370 pub instances: BTreeMap<String, DbInstance>,
371 pub in_progress_instance_ids: HashSet<String>,
372 pub snapshots: BTreeMap<String, DbSnapshot>,
373 pub subnet_groups: BTreeMap<String, DbSubnetGroup>,
374 pub parameter_groups: BTreeMap<String, DbParameterGroup>,
375 #[serde(default)]
382 pub extras: BTreeMap<String, BTreeMap<String, serde_json::Value>>,
383 #[serde(default)]
387 pub events: Vec<RdsEventRecord>,
388 #[serde(default)]
392 pub default_certificate_identifier: Option<String>,
393}
394
395#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
396pub struct RdsEventRecord {
397 pub source_identifier: String,
398 pub source_type: String,
399 pub source_arn: String,
400 pub event_id: String,
401 pub event_categories: Vec<String>,
402 pub message: String,
403 pub date: chrono::DateTime<chrono::Utc>,
404}
405
406#[derive(Debug, Clone, PartialEq, Eq)]
407pub struct EngineVersionInfo {
408 pub engine: String,
409 pub engine_version: String,
410 pub db_parameter_group_family: String,
411 pub db_engine_description: String,
412 pub db_engine_version_description: String,
413 pub status: String,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq)]
417pub struct OrderableDbInstanceOption {
418 pub engine: String,
419 pub engine_version: String,
420 pub db_instance_class: String,
421 pub license_model: String,
422 pub storage_type: String,
423 pub min_storage_size: i32,
424 pub max_storage_size: i32,
425}
426
427#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
428pub struct DbSubnetGroup {
429 pub db_subnet_group_name: String,
430 pub db_subnet_group_arn: String,
431 pub db_subnet_group_description: String,
432 pub vpc_id: String,
433 pub subnet_ids: Vec<String>,
434 pub subnet_availability_zones: Vec<String>,
435 pub tags: Vec<RdsTag>,
436}
437
438#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
439pub struct DbParameterGroup {
440 pub db_parameter_group_name: String,
441 pub db_parameter_group_arn: String,
442 pub db_parameter_group_family: String,
443 pub description: String,
444 pub parameters: BTreeMap<String, String>,
445 #[serde(default)]
449 pub parameter_apply_methods: BTreeMap<String, String>,
450 pub tags: Vec<RdsTag>,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct EngineDefaultParameter {
463 pub name: &'static str,
464 pub value: &'static str,
465 pub apply_type: &'static str,
466 pub data_type: &'static str,
467 pub allowed_values: &'static str,
468 pub is_modifiable: bool,
469}
470
471pub fn engine_default_parameters(family: &str) -> &'static [EngineDefaultParameter] {
478 if family.starts_with("postgres") || family.starts_with("aurora-postgresql") {
479 POSTGRES_DEFAULT_PARAMETERS
480 } else if family.starts_with("mysql") || family.starts_with("aurora-mysql") {
481 MYSQL_DEFAULT_PARAMETERS
482 } else if family.starts_with("mariadb") {
483 MARIADB_DEFAULT_PARAMETERS
484 } else {
485 &[]
486 }
487}
488
489const POSTGRES_DEFAULT_PARAMETERS: &[EngineDefaultParameter] = &[
490 EngineDefaultParameter {
491 name: "max_connections",
492 value: "LEAST({DBInstanceClassMemory/9531392},5000)",
493 apply_type: "static",
494 data_type: "integer",
495 allowed_values: "6-8388607",
496 is_modifiable: true,
497 },
498 EngineDefaultParameter {
499 name: "shared_buffers",
500 value: "{DBInstanceClassMemory/32768}",
501 apply_type: "static",
502 data_type: "integer",
503 allowed_values: "16-1073741823",
504 is_modifiable: true,
505 },
506 EngineDefaultParameter {
507 name: "work_mem",
508 value: "4096",
509 apply_type: "dynamic",
510 data_type: "integer",
511 allowed_values: "64-2147483647",
512 is_modifiable: true,
513 },
514 EngineDefaultParameter {
515 name: "maintenance_work_mem",
516 value: "GREATEST({DBInstanceClassMemory/63963136*1024},65536)",
517 apply_type: "dynamic",
518 data_type: "integer",
519 allowed_values: "1024-2147483647",
520 is_modifiable: true,
521 },
522 EngineDefaultParameter {
523 name: "effective_cache_size",
524 value: "{DBInstanceClassMemory/16384}",
525 apply_type: "dynamic",
526 data_type: "integer",
527 allowed_values: "1-2147483647",
528 is_modifiable: true,
529 },
530];
531
532const MYSQL_DEFAULT_PARAMETERS: &[EngineDefaultParameter] = &[
533 EngineDefaultParameter {
534 name: "max_connections",
535 value: "{DBInstanceClassMemory/12582880}",
536 apply_type: "dynamic",
537 data_type: "integer",
538 allowed_values: "1-100000",
539 is_modifiable: true,
540 },
541 EngineDefaultParameter {
542 name: "innodb_buffer_pool_size",
543 value: "{DBInstanceClassMemory*3/4}",
544 apply_type: "static",
545 data_type: "integer",
546 allowed_values: "5242880-2147483648",
547 is_modifiable: true,
548 },
549 EngineDefaultParameter {
550 name: "max_allowed_packet",
551 value: "67108864",
552 apply_type: "dynamic",
553 data_type: "integer",
554 allowed_values: "1024-1073741824",
555 is_modifiable: true,
556 },
557 EngineDefaultParameter {
558 name: "character_set_server",
559 value: "utf8mb4",
560 apply_type: "dynamic",
561 data_type: "string",
562 allowed_values: "utf8,utf8mb4,latin1",
563 is_modifiable: true,
564 },
565];
566
567const MARIADB_DEFAULT_PARAMETERS: &[EngineDefaultParameter] = &[
568 EngineDefaultParameter {
569 name: "max_connections",
570 value: "{DBInstanceClassMemory/12582880}",
571 apply_type: "dynamic",
572 data_type: "integer",
573 allowed_values: "1-100000",
574 is_modifiable: true,
575 },
576 EngineDefaultParameter {
577 name: "innodb_buffer_pool_size",
578 value: "{DBInstanceClassMemory*3/4}",
579 apply_type: "static",
580 data_type: "integer",
581 allowed_values: "5242880-2147483648",
582 is_modifiable: true,
583 },
584 EngineDefaultParameter {
585 name: "max_allowed_packet",
586 value: "67108864",
587 apply_type: "dynamic",
588 data_type: "integer",
589 allowed_values: "1024-1073741824",
590 is_modifiable: true,
591 },
592];
593
594impl RdsState {
595 pub fn new(account_id: &str, region: &str) -> Self {
596 Self {
597 account_id: account_id.to_string(),
598 region: region.to_string(),
599 instances: BTreeMap::new(),
600 in_progress_instance_ids: HashSet::new(),
601 snapshots: BTreeMap::new(),
602 subnet_groups: BTreeMap::new(),
603 parameter_groups: default_parameter_groups(account_id, region),
604 extras: BTreeMap::new(),
605 events: Vec::new(),
606 default_certificate_identifier: None,
607 }
608 }
609
610 pub fn reset(&mut self) {
611 self.instances.clear();
612 self.in_progress_instance_ids.clear();
613 self.snapshots.clear();
614 self.subnet_groups.clear();
615 self.parameter_groups = default_parameter_groups(&self.account_id, &self.region);
616 self.extras.clear();
617 self.events.clear();
618 self.default_certificate_identifier = None;
619 }
620
621 pub fn push_event(&mut self, event: RdsEventRecord) {
624 const RETENTION_DAYS: i64 = 14;
625 let cutoff = chrono::Utc::now() - chrono::Duration::days(RETENTION_DAYS);
626 self.events.retain(|e| e.date >= cutoff);
627 self.events.push(event);
628 }
629
630 pub fn db_instance_arn(&self, db_instance_identifier: &str) -> String {
631 Arn::new(
632 "rds",
633 &self.region,
634 &self.account_id,
635 &format!("db:{db_instance_identifier}"),
636 )
637 .to_string()
638 }
639
640 pub fn db_snapshot_arn(&self, db_snapshot_identifier: &str) -> String {
641 Arn::new(
642 "rds",
643 &self.region,
644 &self.account_id,
645 &format!("snapshot:{db_snapshot_identifier}"),
646 )
647 .to_string()
648 }
649
650 pub fn db_subnet_group_arn(&self, db_subnet_group_name: &str) -> String {
651 Arn::new(
652 "rds",
653 &self.region,
654 &self.account_id,
655 &format!("subgrp:{db_subnet_group_name}"),
656 )
657 .to_string()
658 }
659
660 pub fn db_parameter_group_arn(&self, db_parameter_group_name: &str) -> String {
661 Arn::new(
662 "rds",
663 &self.region,
664 &self.account_id,
665 &format!("pg:{db_parameter_group_name}"),
666 )
667 .to_string()
668 }
669
670 pub fn next_dbi_resource_id(&self) -> String {
671 format!("db-{}", Uuid::new_v4().simple())
672 }
673
674 pub fn begin_instance_creation(&mut self, db_instance_identifier: &str) -> bool {
675 if self.instances.contains_key(db_instance_identifier)
676 || self
677 .in_progress_instance_ids
678 .contains(db_instance_identifier)
679 {
680 return false;
681 }
682
683 self.in_progress_instance_ids
684 .insert(db_instance_identifier.to_string());
685 true
686 }
687
688 pub fn finish_instance_creation(&mut self, instance: DbInstance) {
689 self.in_progress_instance_ids
690 .remove(&instance.db_instance_identifier);
691 self.instances
692 .insert(instance.db_instance_identifier.clone(), instance);
693 }
694
695 pub fn cancel_instance_creation(&mut self, db_instance_identifier: &str) {
696 self.in_progress_instance_ids.remove(db_instance_identifier);
697 }
698}
699
700pub fn default_engine_versions() -> Vec<EngineVersionInfo> {
701 vec![
702 EngineVersionInfo {
704 engine: "postgres".to_string(),
705 engine_version: "16.3".to_string(),
706 db_parameter_group_family: "postgres16".to_string(),
707 db_engine_description: "PostgreSQL".to_string(),
708 db_engine_version_description: "PostgreSQL 16.3".to_string(),
709 status: "available".to_string(),
710 },
711 EngineVersionInfo {
712 engine: "postgres".to_string(),
713 engine_version: "15.5".to_string(),
714 db_parameter_group_family: "postgres15".to_string(),
715 db_engine_description: "PostgreSQL".to_string(),
716 db_engine_version_description: "PostgreSQL 15.5".to_string(),
717 status: "available".to_string(),
718 },
719 EngineVersionInfo {
720 engine: "postgres".to_string(),
721 engine_version: "14.10".to_string(),
722 db_parameter_group_family: "postgres14".to_string(),
723 db_engine_description: "PostgreSQL".to_string(),
724 db_engine_version_description: "PostgreSQL 14.10".to_string(),
725 status: "available".to_string(),
726 },
727 EngineVersionInfo {
728 engine: "postgres".to_string(),
729 engine_version: "13.13".to_string(),
730 db_parameter_group_family: "postgres13".to_string(),
731 db_engine_description: "PostgreSQL".to_string(),
732 db_engine_version_description: "PostgreSQL 13.13".to_string(),
733 status: "available".to_string(),
734 },
735 EngineVersionInfo {
737 engine: "mysql".to_string(),
738 engine_version: "8.0.35".to_string(),
739 db_parameter_group_family: "mysql8.0".to_string(),
740 db_engine_description: "MySQL Community Edition".to_string(),
741 db_engine_version_description: "MySQL 8.0.35".to_string(),
742 status: "available".to_string(),
743 },
744 EngineVersionInfo {
745 engine: "mysql".to_string(),
746 engine_version: "8.0.28".to_string(),
747 db_parameter_group_family: "mysql8.0".to_string(),
748 db_engine_description: "MySQL Community Edition".to_string(),
749 db_engine_version_description: "MySQL 8.0.28".to_string(),
750 status: "available".to_string(),
751 },
752 EngineVersionInfo {
753 engine: "mysql".to_string(),
754 engine_version: "5.7.44".to_string(),
755 db_parameter_group_family: "mysql5.7".to_string(),
756 db_engine_description: "MySQL Community Edition".to_string(),
757 db_engine_version_description: "MySQL 5.7.44".to_string(),
758 status: "available".to_string(),
759 },
760 EngineVersionInfo {
762 engine: "mariadb".to_string(),
763 engine_version: "11.4.5".to_string(),
764 db_parameter_group_family: "mariadb11.4".to_string(),
765 db_engine_description: "MariaDB Community Edition".to_string(),
766 db_engine_version_description: "MariaDB 11.4.5".to_string(),
767 status: "available".to_string(),
768 },
769 EngineVersionInfo {
770 engine: "mariadb".to_string(),
771 engine_version: "10.11.6".to_string(),
772 db_parameter_group_family: "mariadb10.11".to_string(),
773 db_engine_description: "MariaDB Community Edition".to_string(),
774 db_engine_version_description: "MariaDB 10.11.6".to_string(),
775 status: "available".to_string(),
776 },
777 EngineVersionInfo {
778 engine: "mariadb".to_string(),
779 engine_version: "10.6.16".to_string(),
780 db_parameter_group_family: "mariadb10.6".to_string(),
781 db_engine_description: "MariaDB Community Edition".to_string(),
782 db_engine_version_description: "MariaDB 10.6.16".to_string(),
783 status: "available".to_string(),
784 },
785 ]
786}
787
788pub fn default_orderable_options() -> Vec<OrderableDbInstanceOption> {
789 let mut options = Vec::new();
790 let engines_and_versions = vec![
791 ("postgres", "16.3", "postgresql-license"),
792 ("postgres", "15.5", "postgresql-license"),
793 ("postgres", "14.10", "postgresql-license"),
794 ("postgres", "13.13", "postgresql-license"),
795 ("mysql", "8.0.35", "general-public-license"),
796 ("mysql", "8.0.28", "general-public-license"),
797 ("mysql", "5.7.44", "general-public-license"),
798 ("mariadb", "11.4.5", "general-public-license"),
799 ("mariadb", "10.11.6", "general-public-license"),
800 ("mariadb", "10.6.16", "general-public-license"),
801 ];
802
803 for (engine, version, license) in engines_and_versions {
804 for class in SUPPORTED_INSTANCE_CLASSES {
805 options.push(OrderableDbInstanceOption {
806 engine: engine.to_string(),
807 engine_version: version.to_string(),
808 db_instance_class: class.to_string(),
809 license_model: license.to_string(),
810 storage_type: "gp2".to_string(),
811 min_storage_size: 20,
812 max_storage_size: 16384,
813 });
814 }
815 }
816
817 options
818}
819
820pub fn default_parameter_groups(
821 account_id: &str,
822 region: &str,
823) -> BTreeMap<String, DbParameterGroup> {
824 let mut groups = BTreeMap::new();
825
826 let families = vec![
827 ("postgres16", "Default parameter group for postgres16"),
828 ("postgres15", "Default parameter group for postgres15"),
829 ("postgres14", "Default parameter group for postgres14"),
830 ("postgres13", "Default parameter group for postgres13"),
831 ("mysql8.0", "Default parameter group for mysql8.0"),
832 ("mysql5.7", "Default parameter group for mysql5.7"),
833 ("mariadb11.4", "Default parameter group for mariadb11.4"),
834 ("mariadb10.11", "Default parameter group for mariadb10.11"),
835 ("mariadb10.6", "Default parameter group for mariadb10.6"),
836 ("oracle-ee-23", "Default parameter group for oracle-ee-23"),
841 ("oracle-ee-21", "Default parameter group for oracle-ee-21"),
842 ("oracle-ee-19", "Default parameter group for oracle-ee-19"),
843 ("oracle-se2-23", "Default parameter group for oracle-se2-23"),
844 ("oracle-se2-21", "Default parameter group for oracle-se2-21"),
845 ("oracle-se2-19", "Default parameter group for oracle-se2-19"),
846 (
847 "oracle-ee-cdb-23",
848 "Default parameter group for oracle-ee-cdb-23",
849 ),
850 (
851 "oracle-se2-cdb-23",
852 "Default parameter group for oracle-se2-cdb-23",
853 ),
854 (
855 "sqlserver-ee-16",
856 "Default parameter group for sqlserver-ee-16",
857 ),
858 (
859 "sqlserver-ee-15",
860 "Default parameter group for sqlserver-ee-15",
861 ),
862 (
863 "sqlserver-se-16",
864 "Default parameter group for sqlserver-se-16",
865 ),
866 (
867 "sqlserver-se-15",
868 "Default parameter group for sqlserver-se-15",
869 ),
870 (
871 "sqlserver-ex-16",
872 "Default parameter group for sqlserver-ex-16",
873 ),
874 (
875 "sqlserver-ex-15",
876 "Default parameter group for sqlserver-ex-15",
877 ),
878 (
879 "sqlserver-web-16",
880 "Default parameter group for sqlserver-web-16",
881 ),
882 (
883 "sqlserver-web-15",
884 "Default parameter group for sqlserver-web-15",
885 ),
886 ("db2-se-11.5", "Default parameter group for db2-se-11.5"),
887 ("db2-ae-11.5", "Default parameter group for db2-ae-11.5"),
888 ];
889
890 for (family, description) in families {
891 let group_name = format!("default.{}", family);
892 let group = DbParameterGroup {
893 db_parameter_group_name: group_name.clone(),
894 db_parameter_group_arn: Arn::new(
895 "rds",
896 region,
897 account_id,
898 &format!("pg:{group_name}"),
899 )
900 .to_string(),
901 db_parameter_group_family: family.to_string(),
902 description: description.to_string(),
903 parameters: BTreeMap::new(),
904 parameter_apply_methods: BTreeMap::new(),
905 tags: Vec::new(),
906 };
907 groups.insert(group_name, group);
908 }
909
910 groups
911}
912
913pub const RDS_SNAPSHOT_SCHEMA_VERSION: u32 = 2;
914
915#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
916pub struct RdsSnapshot {
917 pub schema_version: u32,
918 #[serde(default)]
919 pub accounts: Option<fakecloud_core::multi_account::MultiAccountState<RdsState>>,
920 #[serde(default)]
921 pub state: Option<RdsState>,
922}
923
924#[cfg(test)]
925mod tests {
926 use chrono::Utc;
927
928 use super::{
929 default_engine_versions, default_orderable_options, default_parameter_groups, Arn,
930 DbInstance, RdsState,
931 };
932
933 #[test]
934 fn new_initializes_account_and_region() {
935 let state = RdsState::new("123456789012", "us-east-1");
936
937 assert_eq!(state.account_id, "123456789012");
938 assert_eq!(state.region, "us-east-1");
939 assert!(state.instances.is_empty());
940 assert!(state.in_progress_instance_ids.is_empty());
941 }
942
943 #[test]
944 fn reset_clears_instances() {
945 let mut state = RdsState::new("123456789012", "us-east-1");
946 let created_at = Utc::now();
947 state.instances.insert(
948 "db-1".to_string(),
949 DbInstance {
950 db_instance_identifier: "db-1".to_string(),
951 db_instance_arn: "arn:aws:rds:us-east-1:123456789012:db:db-1".to_string(),
952 db_instance_class: "db.t3.micro".to_string(),
953 engine: "postgres".to_string(),
954 engine_version: "16.3".to_string(),
955 db_instance_status: "available".to_string(),
956 master_username: "admin".to_string(),
957 db_name: Some("postgres".to_string()),
958 endpoint_address: "127.0.0.1".to_string(),
959 port: 5432,
960 allocated_storage: 20,
961 publicly_accessible: true,
962 deletion_protection: false,
963 created_at,
964 dbi_resource_id: "db-test".to_string(),
965 master_user_password: "secret123".to_string(),
966 container_id: "container-id".to_string(),
967 host_port: 15432,
968 tags: Vec::new(),
969 read_replica_source_db_instance_identifier: None,
970 read_replica_db_instance_identifiers: Vec::new(),
971 vpc_security_group_ids: Vec::new(),
972 db_parameter_group_name: None,
973 backup_retention_period: 1,
974 preferred_backup_window: "03:00-04:00".to_string(),
975 preferred_maintenance_window: None,
976 latest_restorable_time: Some(created_at),
977 option_group_name: None,
978 multi_az: false,
979 pending_modified_values: None,
980 availability_zone: None,
981 storage_type: None,
982 storage_encrypted: false,
983 kms_key_id: None,
984 iam_database_authentication_enabled: false,
985 iops: None,
986 monitoring_interval: None,
987 monitoring_role_arn: None,
988 performance_insights_enabled: false,
989 performance_insights_kms_key_id: None,
990 performance_insights_retention_period: None,
991 enabled_cloudwatch_logs_exports: Vec::new(),
992 ca_certificate_identifier: None,
993 network_type: None,
994 character_set_name: None,
995 auto_minor_version_upgrade: None,
996 copy_tags_to_snapshot: None,
997 master_user_secret_arn: None,
998 master_user_secret_kms_key_id: None,
999 license_model: None,
1000 max_allocated_storage: None,
1001 multi_tenant: None,
1002 storage_throughput: None,
1003 tde_credential_arn: None,
1004 delete_automated_backups: None,
1005 db_security_groups: Vec::new(),
1006 domain: None,
1007 domain_fqdn: None,
1008 domain_ou: None,
1009 domain_iam_role_name: None,
1010 domain_auth_secret_arn: None,
1011 domain_dns_ips: Vec::new(),
1012 db_cluster_identifier: None,
1013 activity_stream: None,
1014 },
1015 );
1016
1017 state.reset();
1018
1019 assert!(state.instances.is_empty());
1020 assert!(state.in_progress_instance_ids.is_empty());
1021 }
1022
1023 #[test]
1024 fn default_engine_versions_are_postgres_metadata() {
1025 let versions = default_engine_versions();
1026
1027 assert_eq!(versions.len(), 10); assert_eq!(versions[0].engine, "postgres");
1030 assert_eq!(versions[0].engine_version, "16.3");
1031 assert_eq!(versions[0].db_parameter_group_family, "postgres16");
1032 }
1033
1034 #[test]
1035 fn default_orderable_options_match_engine_versions() {
1036 let versions = default_engine_versions();
1037 let options = default_orderable_options();
1038
1039 assert_eq!(options.len(), 70); for version in &versions {
1042 assert!(options.iter().any(|opt| {
1043 opt.engine == version.engine && opt.engine_version == version.engine_version
1044 }));
1045 }
1046 }
1047
1048 #[test]
1049 fn begin_instance_creation_rejects_duplicate_identifiers() {
1050 let mut state = RdsState::new("123456789012", "us-east-1");
1051
1052 assert!(state.begin_instance_creation("db-1"));
1053 assert!(!state.begin_instance_creation("db-1"));
1054
1055 state.cancel_instance_creation("db-1");
1056 assert!(state.begin_instance_creation("db-1"));
1057 }
1058
1059 #[test]
1060 fn arn_helpers_format_correctly() {
1061 let state = RdsState::new("123456789012", "eu-west-1");
1062 assert!(state.db_instance_arn("mydb").contains(":db:mydb"));
1063 assert!(state.db_snapshot_arn("snap1").contains(":snapshot:snap1"));
1064 assert!(state.db_subnet_group_arn("sng").contains("sng"));
1065 assert!(state.db_parameter_group_arn("pg").contains("pg"));
1066 }
1067
1068 #[test]
1069 fn next_dbi_resource_id_format() {
1070 let state = RdsState::new("123456789012", "us-east-1");
1071 let id = state.next_dbi_resource_id();
1072 assert!(id.starts_with("db-"));
1073 assert!(id.len() > 3);
1074 }
1075
1076 #[test]
1077 fn default_engine_versions_list_not_empty() {
1078 let versions = default_engine_versions();
1079 assert!(!versions.is_empty());
1080 }
1081
1082 #[test]
1083 fn default_orderable_options_list_not_empty() {
1084 let opts = default_orderable_options();
1085 assert!(!opts.is_empty());
1086 }
1087
1088 #[test]
1089 fn default_parameter_groups_returned_per_family() {
1090 let groups = default_parameter_groups("123456789012", "us-east-1");
1091 assert!(!groups.is_empty());
1092 }
1093
1094 fn make_instance(id: &str) -> DbInstance {
1095 let created_at = Utc::now();
1096 DbInstance {
1097 db_instance_identifier: id.to_string(),
1098 db_instance_arn: Arn::new("rds", "us-east-1", "123", &format!("db:{id}")).to_string(),
1099 db_instance_class: "db.t3.micro".to_string(),
1100 engine: "postgres".to_string(),
1101 engine_version: "16.3".to_string(),
1102 db_instance_status: "available".to_string(),
1103 master_username: "admin".to_string(),
1104 db_name: None,
1105 endpoint_address: "x".to_string(),
1106 port: 5432,
1107 allocated_storage: 20,
1108 publicly_accessible: false,
1109 deletion_protection: false,
1110 created_at,
1111 dbi_resource_id: "d".to_string(),
1112 master_user_password: "p".to_string(),
1113 container_id: "c".to_string(),
1114 host_port: 0,
1115 tags: Vec::new(),
1116 read_replica_source_db_instance_identifier: None,
1117 read_replica_db_instance_identifiers: Vec::new(),
1118 vpc_security_group_ids: Vec::new(),
1119 db_parameter_group_name: None,
1120 backup_retention_period: 0,
1121 preferred_backup_window: String::new(),
1122 preferred_maintenance_window: None,
1123 latest_restorable_time: None,
1124 option_group_name: None,
1125 multi_az: false,
1126 pending_modified_values: None,
1127 availability_zone: None,
1128 storage_type: None,
1129 storage_encrypted: false,
1130 kms_key_id: None,
1131 iam_database_authentication_enabled: false,
1132 iops: None,
1133 monitoring_interval: None,
1134 monitoring_role_arn: None,
1135 performance_insights_enabled: false,
1136 performance_insights_kms_key_id: None,
1137 performance_insights_retention_period: None,
1138 enabled_cloudwatch_logs_exports: Vec::new(),
1139 ca_certificate_identifier: None,
1140 network_type: None,
1141 character_set_name: None,
1142 auto_minor_version_upgrade: None,
1143 copy_tags_to_snapshot: None,
1144 master_user_secret_arn: None,
1145 master_user_secret_kms_key_id: None,
1146 license_model: None,
1147 max_allocated_storage: None,
1148 multi_tenant: None,
1149 storage_throughput: None,
1150 tde_credential_arn: None,
1151 delete_automated_backups: None,
1152 db_security_groups: Vec::new(),
1153 domain: None,
1154 domain_fqdn: None,
1155 domain_ou: None,
1156 domain_iam_role_name: None,
1157 domain_auth_secret_arn: None,
1158 domain_dns_ips: Vec::new(),
1159 db_cluster_identifier: None,
1160 activity_stream: None,
1161 }
1162 }
1163
1164 #[test]
1165 fn finish_instance_creation_moves_from_pending_to_instances() {
1166 let mut state = RdsState::new("123456789012", "us-east-1");
1167 assert!(state.begin_instance_creation("db-x"));
1168 assert!(state.in_progress_instance_ids.contains("db-x"));
1169 state.finish_instance_creation(make_instance("db-x"));
1170 assert!(!state.in_progress_instance_ids.contains("db-x"));
1171 assert!(state.instances.contains_key("db-x"));
1172 }
1173
1174 #[test]
1175 fn cancel_instance_creation_drops_pending() {
1176 let mut state = RdsState::new("123456789012", "us-east-1");
1177 state.begin_instance_creation("db-y");
1178 state.cancel_instance_creation("db-y");
1179 assert!(!state.in_progress_instance_ids.contains("db-y"));
1180 }
1181
1182 #[test]
1183 fn begin_instance_creation_rejects_when_already_created() {
1184 let mut state = RdsState::new("123456789012", "us-east-1");
1185 state
1186 .instances
1187 .insert("db-z".to_string(), make_instance("db-z"));
1188 assert!(!state.begin_instance_creation("db-z"));
1189 }
1190
1191 #[test]
1192 fn reset_restores_default_parameter_groups() {
1193 let mut state = RdsState::new("123456789012", "us-east-1");
1194 state.parameter_groups.clear();
1195 state.reset();
1196 assert!(!state.parameter_groups.is_empty());
1197 }
1198
1199 #[test]
1200 fn arn_helpers_include_region_and_account() {
1201 let state = RdsState::new("111122223333", "ap-southeast-2");
1202 let arn = state.db_instance_arn("my-db");
1203 assert!(arn.contains("111122223333"));
1204 assert!(arn.contains("ap-southeast-2"));
1205 let snap = state.db_snapshot_arn("snap");
1206 assert!(snap.contains("snapshot:snap"));
1207 }
1208
1209 #[test]
1210 fn next_dbi_resource_id_unique_across_calls() {
1211 let state = RdsState::new("123", "us-east-1");
1212 let a = state.next_dbi_resource_id();
1213 let b = state.next_dbi_resource_id();
1214 assert_ne!(a, b);
1215 }
1216}