"""Hard task: Enterprise FinOps — 40 resources with complex dependencies and blast radius.""" from env.models import ActionType from env.tasks.base_task import BaseTask class EnterpriseFinOpsTask(BaseTask): task_id = "enterprise-finops" difficulty = "hard" max_steps = 45 description = ( "Full FinOps review of an enterprise AWS account with 40 resources. " "Complex dependencies, blast radius considerations, reserved instance management, " "and multi-service optimization across prod/staging/dev." ) data_file = "hard_account.json" optimal_cost = 7100.0 # approximate after optimizations def get_correct_actions(self) -> dict[str, ActionType]: return { # --- PROD EC2 FLEET behind ELB: SKIP (critical) --- "res-hard-001": ActionType.skip_resource, # prod-web-1 (fleet behind ALB) "res-hard-002": ActionType.skip_resource, # prod-web-2 (fleet behind ALB) "res-hard-003": ActionType.skip_resource, # prod-web-3 (fleet behind ALB) # --- DEV/STAGING EC2: OPTIMIZE --- # dev-data-science: r5.2xlarge @ 2% -> rightsize to t3.small # saves $367.92 - $15.18 = $352.74 "res-hard-004": ActionType.rightsize_resource, # staging-api-server: m5.xlarge @ 8% -> rightsize to t3.small # saves $140.16 - $15.18 = $124.98 "res-hard-005": ActionType.rightsize_resource, # dev-build-server: c5.2xlarge @ 12% -> rightsize to c5.large # saves $248.20 - $62.05 = $186.15 "res-hard-006": ActionType.rightsize_resource, # prod-batch-processor: fault-tolerant, spot eligible -> purchase spot # saves ~70% of $124.10 = $86.87 "res-hard-007": ActionType.purchase_reservation, # dev-sandbox: t3.xlarge @ 1%, rarely used -> terminate # saves $121.47 "res-hard-008": ActionType.terminate_resource, # --- LOAD BALANCERS --- "res-hard-009": ActionType.skip_resource, # prod-web-alb: active, critical # staging-old-alb: 0 targets -> terminate "res-hard-010": ActionType.terminate_resource, # saves $22.27 # dev-test-alb: 0 targets -> terminate "res-hard-011": ActionType.terminate_resource, # saves $22.27 # --- RDS --- "res-hard-012": ActionType.skip_resource, # prod-main-db: active, critical "res-hard-013": ActionType.skip_resource, # prod-db-read-replica: critical, depends on primary # dev-analytics-db: db.r5.xlarge + 1TB @ 3%, 15GB used -> rightsize # saves $465.40 - ($12.41 + 20*0.10) = $465.40 - $14.41 = $450.99 "res-hard-014": ActionType.rightsize_resource, # staging-app-db: db.m5.xlarge + 500GB @ 10% -> rightsize to db.t3.medium + 100GB # saves $308.62 - ($49.64 + 100*0.10) = $308.62 - $59.64 = $248.98 "res-hard-015": ActionType.rightsize_resource, # --- S3 --- "res-hard-016": ActionType.skip_resource, # prod-media-assets: frequently accessed, critical # staging-backup-archive: 20TB rarely accessed -> lifecycle policy # saves ~70% of $460.00 = $322.00 "res-hard-017": ActionType.add_lifecycle_policy, # dev-log-bucket: 40TB never accessed -> lifecycle policy # saves ~70% of $920.00 = $644.00 "res-hard-018": ActionType.add_lifecycle_policy, # --- KUBERNETES --- "res-hard-019": ActionType.skip_resource, # prod-k8s: active, critical, autoscaling # dev-k8s: 8 nodes @ 8%, runs 24/7 -> schedule weekday-only # saves ~28% of $633.64 = $177.42 "res-hard-020": ActionType.schedule_uptime, # --- NAT GATEWAYS --- "res-hard-021": ActionType.skip_resource, # prod-nat: critical, prod subnet depends on it # dev-nat: replaceable with VPC endpoint # saves $35.10 "res-hard-022": ActionType.terminate_resource, # --- ELASTICSEARCH --- "res-hard-023": ActionType.skip_resource, # prod-search: active, critical # dev-logging-es: 12 m5.xlarge nodes for 200 docs -> rightsize to 1 t3.small # saves $2890.80 - $26.28 = $2864.52 "res-hard-024": ActionType.rightsize_resource, # --- RESERVED INSTANCES --- "res-hard-025": ActionType.skip_resource, # prod-app-ri-1: correct region, critical "res-hard-026": ActionType.skip_resource, # prod-app-ri-2: correct region, critical # staging-ri-wrong-region-1: RI in us-west-2 but instance in us-east-1 # -> request_more_info to modify RI region "res-hard-027": ActionType.request_more_info, # staging-ri-wrong-region-2: RI in eu-west-1 but instance in us-east-1 # -> request_more_info to modify RI region "res-hard-028": ActionType.request_more_info, # --- CLOUDWATCH/MONITORING S3 --- # cloudwatch-logs-prod: 20TB no lifecycle -> add policy (but prod, non-critical) # saves ~70% of $460.00 = $322.00 "res-hard-029": ActionType.add_lifecycle_policy, # cloudwatch-logs-dev: 15TB never accessed -> lifecycle policy # saves ~70% of $345.00 = $241.50 "res-hard-030": ActionType.add_lifecycle_policy, # metrics-archive-staging: 8TB rarely accessed -> lifecycle policy # saves ~70% of $184.00 = $128.80 "res-hard-031": ActionType.add_lifecycle_policy, # cloudtrail-logs: compliance audit trail, critical -> skip "res-hard-032": ActionType.skip_resource, # --- ADDITIONAL RESOURCES --- # orphaned-ebs-volume-1: detached, unused -> terminate # saves $500.00 "res-hard-033": ActionType.terminate_resource, # orphaned-ebs-volume-2: detached, unused -> terminate # saves $375.00 "res-hard-034": ActionType.terminate_resource, # unused-eip-1 -> terminate (saves $3.65) "res-hard-035": ActionType.terminate_resource, # unused-eip-2 -> terminate (saves $3.65) "res-hard-036": ActionType.terminate_resource, # prod-data-sync: redundant replication (DR handled by RDS multi-AZ) # -> terminate replication, keep instance # saves data transfer: $600.00 "res-hard-037": ActionType.terminate_resource, # dev-etl-worker: idle, rarely used -> terminate # saves $60.74 "res-hard-038": ActionType.terminate_resource, # prod-backup-replica-eu: redundant cross-region backup # -> change_storage_class to Glacier Deep Archive # saves most of $430.00 (keep at ~$10) "res-hard-039": ActionType.change_storage_class, # dev-legacy-monolith: r5.xlarge @ 3%, scheduled for decommission -> terminate # saves $183.96 "res-hard-040": ActionType.terminate_resource, } def get_critical_resources(self) -> set[str]: return { "res-hard-001", "res-hard-002", "res-hard-003", # prod web fleet "res-hard-009", # prod ALB "res-hard-012", "res-hard-013", # prod RDS "res-hard-016", # prod S3 media "res-hard-019", # prod K8s "res-hard-021", # prod NAT GW "res-hard-023", # prod ES "res-hard-025", "res-hard-026", # prod RI correct region "res-hard-032", # cloudtrail compliance } def get_action_savings(self) -> dict[str, float]: return { "res-hard-001": 0.0, # skip "res-hard-002": 0.0, # skip "res-hard-003": 0.0, # skip "res-hard-004": 352.74, # rightsize "res-hard-005": 124.98, # rightsize "res-hard-006": 186.15, # rightsize "res-hard-007": 86.87, # spot/reservation "res-hard-008": 121.47, # terminate "res-hard-009": 0.0, # skip "res-hard-010": 22.27, # terminate "res-hard-011": 22.27, # terminate "res-hard-012": 0.0, # skip "res-hard-013": 0.0, # skip "res-hard-014": 450.99, # rightsize "res-hard-015": 248.98, # rightsize "res-hard-016": 0.0, # skip "res-hard-017": 322.00, # lifecycle "res-hard-018": 644.00, # lifecycle "res-hard-019": 0.0, # skip "res-hard-020": 177.42, # schedule "res-hard-021": 0.0, # skip "res-hard-022": 35.10, # terminate NAT -> VPC endpoint "res-hard-023": 0.0, # skip "res-hard-024": 2864.52, # rightsize ES massively "res-hard-025": 0.0, # skip "res-hard-026": 0.0, # skip "res-hard-027": 0.0, # request_more_info (no direct savings) "res-hard-028": 0.0, # request_more_info (no direct savings) "res-hard-029": 322.00, # lifecycle "res-hard-030": 241.50, # lifecycle "res-hard-031": 128.80, # lifecycle "res-hard-032": 0.0, # skip "res-hard-033": 500.00, # terminate orphaned EBS "res-hard-034": 375.00, # terminate orphaned EBS "res-hard-035": 3.65, # terminate unused EIP "res-hard-036": 3.65, # terminate unused EIP "res-hard-037": 600.00, # terminate redundant replication "res-hard-038": 60.74, # terminate idle "res-hard-039": 420.00, # change to Glacier Deep Archive "res-hard-040": 183.96, # terminate decommission target }