1use crate::{GpuSpec, MachineProfile, Platform};
13
14pub fn parse_cpu(s: &str) -> Result<f64, String> {
22 let s = s.trim();
23 if s.is_empty() {
24 return Err("empty CPU string".to_string());
25 }
26
27 if let Some(millis) = s.strip_suffix('m') {
28 let v: f64 = millis
29 .parse()
30 .map_err(|_| format!("invalid CPU millicore value: '{s}'"))?;
31 Ok(v / 1000.0)
32 } else {
33 s.parse().map_err(|_| format!("invalid CPU value: '{s}'"))
34 }
35}
36
37pub fn parse_memory_bytes(s: &str) -> Result<u64, String> {
42 let s = s.trim();
43 if s.is_empty() {
44 return Err("empty memory/storage string".to_string());
45 }
46
47 if let Some(num) = s.strip_suffix("Ti") {
49 let v: f64 = num
50 .parse()
51 .map_err(|_| format!("invalid memory value: '{s}'"))?;
52 return Ok((v * 1024.0 * 1024.0 * 1024.0 * 1024.0) as u64);
53 }
54 if let Some(num) = s.strip_suffix("Gi") {
55 let v: f64 = num
56 .parse()
57 .map_err(|_| format!("invalid memory value: '{s}'"))?;
58 return Ok((v * 1024.0 * 1024.0 * 1024.0) as u64);
59 }
60 if let Some(num) = s.strip_suffix("Mi") {
61 let v: f64 = num
62 .parse()
63 .map_err(|_| format!("invalid memory value: '{s}'"))?;
64 return Ok((v * 1024.0 * 1024.0) as u64);
65 }
66 if let Some(num) = s.strip_suffix("Ki") {
67 let v: f64 = num
68 .parse()
69 .map_err(|_| format!("invalid memory value: '{s}'"))?;
70 return Ok((v * 1024.0) as u64);
71 }
72
73 if let Some(num) = s.strip_suffix('T') {
75 let v: f64 = num
76 .parse()
77 .map_err(|_| format!("invalid memory value: '{s}'"))?;
78 return Ok((v * 1_000_000_000_000.0) as u64);
79 }
80 if let Some(num) = s.strip_suffix('G') {
81 let v: f64 = num
82 .parse()
83 .map_err(|_| format!("invalid memory value: '{s}'"))?;
84 return Ok((v * 1_000_000_000.0) as u64);
85 }
86 if let Some(num) = s.strip_suffix('M') {
87 let v: f64 = num
88 .parse()
89 .map_err(|_| format!("invalid memory value: '{s}'"))?;
90 return Ok((v * 1_000_000.0) as u64);
91 }
92 if let Some(num) = s.strip_suffix('k') {
93 let v: f64 = num
94 .parse()
95 .map_err(|_| format!("invalid memory value: '{s}'"))?;
96 return Ok((v * 1000.0) as u64);
97 }
98
99 s.parse()
101 .map_err(|_| format!("invalid memory value: '{s}'"))
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum InstanceFamily {
111 Burstable,
112 GeneralPurpose,
113 ComputeOptimized,
114 MemoryOptimized,
115 StorageOptimized,
116 GpuCompute,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum Architecture {
122 Arm64,
123 X86_64,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub struct CatalogGpu {
129 pub gpu_type: &'static str,
130 pub count: u32,
131}
132
133#[derive(Debug, Clone)]
137pub struct InstanceTypeSpec {
138 pub name: &'static str,
139 pub platform: Platform,
140 pub family: InstanceFamily,
141 pub architecture: Architecture,
142 pub vcpu: u32,
144 pub memory_bytes: u64,
146 pub ephemeral_storage_bytes: u64,
148 pub gpu: Option<CatalogGpu>,
150}
151
152impl InstanceTypeSpec {
153 pub fn to_machine_profile(&self) -> MachineProfile {
155 MachineProfile {
156 cpu: format!("{}.0", self.vcpu),
157 memory_bytes: self.memory_bytes,
158 ephemeral_storage_bytes: self.ephemeral_storage_bytes,
159 gpu: self.gpu.map(|g| GpuSpec {
160 gpu_type: g.gpu_type.to_string(),
161 count: g.count,
162 }),
163 }
164 }
165}
166
167const KI: u64 = 1024;
169const MI: u64 = KI * 1024;
170const GI: u64 = MI * 1024;
171
172static CATALOG: &[InstanceTypeSpec] = &[
180 InstanceTypeSpec {
186 name: "t4g.micro",
187 platform: Platform::Aws,
188 family: InstanceFamily::Burstable,
189 architecture: Architecture::Arm64,
190 vcpu: 2,
191 memory_bytes: 1 * GI,
192 ephemeral_storage_bytes: 20 * GI,
193 gpu: None,
194 },
195 InstanceTypeSpec {
196 name: "t4g.small",
197 platform: Platform::Aws,
198 family: InstanceFamily::Burstable,
199 architecture: Architecture::Arm64,
200 vcpu: 2,
201 memory_bytes: 2 * GI,
202 ephemeral_storage_bytes: 20 * GI,
203 gpu: None,
204 },
205 InstanceTypeSpec {
206 name: "t4g.medium",
207 platform: Platform::Aws,
208 family: InstanceFamily::Burstable,
209 architecture: Architecture::Arm64,
210 vcpu: 2,
211 memory_bytes: 4 * GI,
212 ephemeral_storage_bytes: 20 * GI,
213 gpu: None,
214 },
215 InstanceTypeSpec {
216 name: "t4g.large",
217 platform: Platform::Aws,
218 family: InstanceFamily::Burstable,
219 architecture: Architecture::Arm64,
220 vcpu: 2,
221 memory_bytes: 8 * GI,
222 ephemeral_storage_bytes: 20 * GI,
223 gpu: None,
224 },
225 InstanceTypeSpec {
226 name: "t4g.xlarge",
227 platform: Platform::Aws,
228 family: InstanceFamily::Burstable,
229 architecture: Architecture::Arm64,
230 vcpu: 4,
231 memory_bytes: 16 * GI,
232 ephemeral_storage_bytes: 20 * GI,
233 gpu: None,
234 },
235 InstanceTypeSpec {
237 name: "m7g.medium",
238 platform: Platform::Aws,
239 family: InstanceFamily::GeneralPurpose,
240 architecture: Architecture::Arm64,
241 vcpu: 1,
242 memory_bytes: 4 * GI,
243 ephemeral_storage_bytes: 20 * GI,
244 gpu: None,
245 },
246 InstanceTypeSpec {
247 name: "m7g.large",
248 platform: Platform::Aws,
249 family: InstanceFamily::GeneralPurpose,
250 architecture: Architecture::Arm64,
251 vcpu: 2,
252 memory_bytes: 8 * GI,
253 ephemeral_storage_bytes: 20 * GI,
254 gpu: None,
255 },
256 InstanceTypeSpec {
257 name: "m7g.xlarge",
258 platform: Platform::Aws,
259 family: InstanceFamily::GeneralPurpose,
260 architecture: Architecture::Arm64,
261 vcpu: 4,
262 memory_bytes: 16 * GI,
263 ephemeral_storage_bytes: 20 * GI,
264 gpu: None,
265 },
266 InstanceTypeSpec {
267 name: "m7g.2xlarge",
268 platform: Platform::Aws,
269 family: InstanceFamily::GeneralPurpose,
270 architecture: Architecture::Arm64,
271 vcpu: 8,
272 memory_bytes: 32 * GI,
273 ephemeral_storage_bytes: 20 * GI,
274 gpu: None,
275 },
276 InstanceTypeSpec {
277 name: "m7g.4xlarge",
278 platform: Platform::Aws,
279 family: InstanceFamily::GeneralPurpose,
280 architecture: Architecture::Arm64,
281 vcpu: 16,
282 memory_bytes: 64 * GI,
283 ephemeral_storage_bytes: 20 * GI,
284 gpu: None,
285 },
286 InstanceTypeSpec {
288 name: "c7g.medium",
289 platform: Platform::Aws,
290 family: InstanceFamily::ComputeOptimized,
291 architecture: Architecture::Arm64,
292 vcpu: 1,
293 memory_bytes: 2 * GI,
294 ephemeral_storage_bytes: 20 * GI,
295 gpu: None,
296 },
297 InstanceTypeSpec {
298 name: "c7g.large",
299 platform: Platform::Aws,
300 family: InstanceFamily::ComputeOptimized,
301 architecture: Architecture::Arm64,
302 vcpu: 2,
303 memory_bytes: 4 * GI,
304 ephemeral_storage_bytes: 20 * GI,
305 gpu: None,
306 },
307 InstanceTypeSpec {
308 name: "c7g.xlarge",
309 platform: Platform::Aws,
310 family: InstanceFamily::ComputeOptimized,
311 architecture: Architecture::Arm64,
312 vcpu: 4,
313 memory_bytes: 8 * GI,
314 ephemeral_storage_bytes: 20 * GI,
315 gpu: None,
316 },
317 InstanceTypeSpec {
318 name: "c7g.2xlarge",
319 platform: Platform::Aws,
320 family: InstanceFamily::ComputeOptimized,
321 architecture: Architecture::Arm64,
322 vcpu: 8,
323 memory_bytes: 16 * GI,
324 ephemeral_storage_bytes: 20 * GI,
325 gpu: None,
326 },
327 InstanceTypeSpec {
328 name: "c7g.4xlarge",
329 platform: Platform::Aws,
330 family: InstanceFamily::ComputeOptimized,
331 architecture: Architecture::Arm64,
332 vcpu: 16,
333 memory_bytes: 32 * GI,
334 ephemeral_storage_bytes: 20 * GI,
335 gpu: None,
336 },
337 InstanceTypeSpec {
339 name: "r7g.medium",
340 platform: Platform::Aws,
341 family: InstanceFamily::MemoryOptimized,
342 architecture: Architecture::Arm64,
343 vcpu: 1,
344 memory_bytes: 8 * GI,
345 ephemeral_storage_bytes: 20 * GI,
346 gpu: None,
347 },
348 InstanceTypeSpec {
349 name: "r7g.large",
350 platform: Platform::Aws,
351 family: InstanceFamily::MemoryOptimized,
352 architecture: Architecture::Arm64,
353 vcpu: 2,
354 memory_bytes: 16 * GI,
355 ephemeral_storage_bytes: 20 * GI,
356 gpu: None,
357 },
358 InstanceTypeSpec {
359 name: "r7g.xlarge",
360 platform: Platform::Aws,
361 family: InstanceFamily::MemoryOptimized,
362 architecture: Architecture::Arm64,
363 vcpu: 4,
364 memory_bytes: 32 * GI,
365 ephemeral_storage_bytes: 20 * GI,
366 gpu: None,
367 },
368 InstanceTypeSpec {
369 name: "r7g.2xlarge",
370 platform: Platform::Aws,
371 family: InstanceFamily::MemoryOptimized,
372 architecture: Architecture::Arm64,
373 vcpu: 8,
374 memory_bytes: 64 * GI,
375 ephemeral_storage_bytes: 20 * GI,
376 gpu: None,
377 },
378 InstanceTypeSpec {
379 name: "r7g.4xlarge",
380 platform: Platform::Aws,
381 family: InstanceFamily::MemoryOptimized,
382 architecture: Architecture::Arm64,
383 vcpu: 16,
384 memory_bytes: 128 * GI,
385 ephemeral_storage_bytes: 20 * GI,
386 gpu: None,
387 },
388 InstanceTypeSpec {
390 name: "i4i.xlarge",
391 platform: Platform::Aws,
392 family: InstanceFamily::StorageOptimized,
393 architecture: Architecture::X86_64,
394 vcpu: 4,
395 memory_bytes: 32 * GI,
396 ephemeral_storage_bytes: 937 * GI,
397 gpu: None,
398 },
399 InstanceTypeSpec {
400 name: "i4i.2xlarge",
401 platform: Platform::Aws,
402 family: InstanceFamily::StorageOptimized,
403 architecture: Architecture::X86_64,
404 vcpu: 8,
405 memory_bytes: 64 * GI,
406 ephemeral_storage_bytes: 1875 * GI,
407 gpu: None,
408 },
409 InstanceTypeSpec {
410 name: "i4i.4xlarge",
411 platform: Platform::Aws,
412 family: InstanceFamily::StorageOptimized,
413 architecture: Architecture::X86_64,
414 vcpu: 16,
415 memory_bytes: 128 * GI,
416 ephemeral_storage_bytes: 3750 * GI,
417 gpu: None,
418 },
419 InstanceTypeSpec {
420 name: "i4i.8xlarge",
421 platform: Platform::Aws,
422 family: InstanceFamily::StorageOptimized,
423 architecture: Architecture::X86_64,
424 vcpu: 32,
425 memory_bytes: 256 * GI,
426 ephemeral_storage_bytes: 7500 * GI,
427 gpu: None,
428 },
429 InstanceTypeSpec {
431 name: "g5.xlarge",
432 platform: Platform::Aws,
433 family: InstanceFamily::GpuCompute,
434 architecture: Architecture::X86_64,
435 vcpu: 4,
436 memory_bytes: 16 * GI,
437 ephemeral_storage_bytes: 250 * GI,
438 gpu: Some(CatalogGpu {
439 gpu_type: "nvidia-t4",
440 count: 1,
441 }),
442 },
443 InstanceTypeSpec {
444 name: "g5.2xlarge",
445 platform: Platform::Aws,
446 family: InstanceFamily::GpuCompute,
447 architecture: Architecture::X86_64,
448 vcpu: 8,
449 memory_bytes: 32 * GI,
450 ephemeral_storage_bytes: 450 * GI,
451 gpu: Some(CatalogGpu {
452 gpu_type: "nvidia-t4",
453 count: 1,
454 }),
455 },
456 InstanceTypeSpec {
458 name: "p4d.24xlarge",
459 platform: Platform::Aws,
460 family: InstanceFamily::GpuCompute,
461 architecture: Architecture::X86_64,
462 vcpu: 96,
463 memory_bytes: 1152 * GI,
464 ephemeral_storage_bytes: 8000 * GI,
465 gpu: Some(CatalogGpu {
466 gpu_type: "nvidia-a100",
467 count: 8,
468 }),
469 },
470 InstanceTypeSpec {
472 name: "p5.48xlarge",
473 platform: Platform::Aws,
474 family: InstanceFamily::GpuCompute,
475 architecture: Architecture::X86_64,
476 vcpu: 192,
477 memory_bytes: 2048 * GI,
478 ephemeral_storage_bytes: 8000 * GI,
479 gpu: Some(CatalogGpu {
480 gpu_type: "nvidia-h100",
481 count: 8,
482 }),
483 },
484 InstanceTypeSpec {
490 name: "e2-micro",
491 platform: Platform::Gcp,
492 family: InstanceFamily::Burstable,
493 architecture: Architecture::X86_64,
494 vcpu: 2,
495 memory_bytes: 1 * GI,
496 ephemeral_storage_bytes: 20 * GI,
497 gpu: None,
498 },
499 InstanceTypeSpec {
500 name: "e2-small",
501 platform: Platform::Gcp,
502 family: InstanceFamily::Burstable,
503 architecture: Architecture::X86_64,
504 vcpu: 2,
505 memory_bytes: 2 * GI,
506 ephemeral_storage_bytes: 20 * GI,
507 gpu: None,
508 },
509 InstanceTypeSpec {
510 name: "e2-medium",
511 platform: Platform::Gcp,
512 family: InstanceFamily::Burstable,
513 architecture: Architecture::X86_64,
514 vcpu: 2,
515 memory_bytes: 4 * GI,
516 ephemeral_storage_bytes: 20 * GI,
517 gpu: None,
518 },
519 InstanceTypeSpec {
521 name: "n2-standard-2",
522 platform: Platform::Gcp,
523 family: InstanceFamily::GeneralPurpose,
524 architecture: Architecture::X86_64,
525 vcpu: 2,
526 memory_bytes: 8 * GI,
527 ephemeral_storage_bytes: 20 * GI,
528 gpu: None,
529 },
530 InstanceTypeSpec {
531 name: "n2-standard-4",
532 platform: Platform::Gcp,
533 family: InstanceFamily::GeneralPurpose,
534 architecture: Architecture::X86_64,
535 vcpu: 4,
536 memory_bytes: 16 * GI,
537 ephemeral_storage_bytes: 20 * GI,
538 gpu: None,
539 },
540 InstanceTypeSpec {
541 name: "n2-standard-8",
542 platform: Platform::Gcp,
543 family: InstanceFamily::GeneralPurpose,
544 architecture: Architecture::X86_64,
545 vcpu: 8,
546 memory_bytes: 32 * GI,
547 ephemeral_storage_bytes: 20 * GI,
548 gpu: None,
549 },
550 InstanceTypeSpec {
551 name: "n2-standard-16",
552 platform: Platform::Gcp,
553 family: InstanceFamily::GeneralPurpose,
554 architecture: Architecture::X86_64,
555 vcpu: 16,
556 memory_bytes: 64 * GI,
557 ephemeral_storage_bytes: 20 * GI,
558 gpu: None,
559 },
560 InstanceTypeSpec {
562 name: "c3-standard-4",
563 platform: Platform::Gcp,
564 family: InstanceFamily::ComputeOptimized,
565 architecture: Architecture::X86_64,
566 vcpu: 4,
567 memory_bytes: 8 * GI,
568 ephemeral_storage_bytes: 20 * GI,
569 gpu: None,
570 },
571 InstanceTypeSpec {
572 name: "c3-standard-8",
573 platform: Platform::Gcp,
574 family: InstanceFamily::ComputeOptimized,
575 architecture: Architecture::X86_64,
576 vcpu: 8,
577 memory_bytes: 16 * GI,
578 ephemeral_storage_bytes: 20 * GI,
579 gpu: None,
580 },
581 InstanceTypeSpec {
583 name: "n2-highmem-2",
584 platform: Platform::Gcp,
585 family: InstanceFamily::MemoryOptimized,
586 architecture: Architecture::X86_64,
587 vcpu: 2,
588 memory_bytes: 16 * GI,
589 ephemeral_storage_bytes: 20 * GI,
590 gpu: None,
591 },
592 InstanceTypeSpec {
593 name: "n2-highmem-4",
594 platform: Platform::Gcp,
595 family: InstanceFamily::MemoryOptimized,
596 architecture: Architecture::X86_64,
597 vcpu: 4,
598 memory_bytes: 32 * GI,
599 ephemeral_storage_bytes: 20 * GI,
600 gpu: None,
601 },
602 InstanceTypeSpec {
603 name: "n2-highmem-8",
604 platform: Platform::Gcp,
605 family: InstanceFamily::MemoryOptimized,
606 architecture: Architecture::X86_64,
607 vcpu: 8,
608 memory_bytes: 64 * GI,
609 ephemeral_storage_bytes: 20 * GI,
610 gpu: None,
611 },
612 InstanceTypeSpec {
613 name: "n2-highmem-16",
614 platform: Platform::Gcp,
615 family: InstanceFamily::MemoryOptimized,
616 architecture: Architecture::X86_64,
617 vcpu: 16,
618 memory_bytes: 128 * GI,
619 ephemeral_storage_bytes: 20 * GI,
620 gpu: None,
621 },
622 InstanceTypeSpec {
623 name: "n2-highmem-32",
624 platform: Platform::Gcp,
625 family: InstanceFamily::MemoryOptimized,
626 architecture: Architecture::X86_64,
627 vcpu: 32,
628 memory_bytes: 256 * GI,
629 ephemeral_storage_bytes: 20 * GI,
630 gpu: None,
631 },
632 InstanceTypeSpec {
634 name: "c3d-standard-8",
635 platform: Platform::Gcp,
636 family: InstanceFamily::StorageOptimized,
637 architecture: Architecture::X86_64,
638 vcpu: 8,
639 memory_bytes: 32 * GI,
640 ephemeral_storage_bytes: 480 * GI,
641 gpu: None,
642 },
643 InstanceTypeSpec {
644 name: "c3d-standard-16",
645 platform: Platform::Gcp,
646 family: InstanceFamily::StorageOptimized,
647 architecture: Architecture::X86_64,
648 vcpu: 16,
649 memory_bytes: 64 * GI,
650 ephemeral_storage_bytes: 960 * GI,
651 gpu: None,
652 },
653 InstanceTypeSpec {
654 name: "c3d-standard-30",
655 platform: Platform::Gcp,
656 family: InstanceFamily::StorageOptimized,
657 architecture: Architecture::X86_64,
658 vcpu: 30,
659 memory_bytes: 120 * GI,
660 ephemeral_storage_bytes: 1920 * GI,
661 gpu: None,
662 },
663 InstanceTypeSpec {
665 name: "n1-standard-4-t4",
666 platform: Platform::Gcp,
667 family: InstanceFamily::GpuCompute,
668 architecture: Architecture::X86_64,
669 vcpu: 4,
670 memory_bytes: 15 * GI,
671 ephemeral_storage_bytes: 100 * GI,
672 gpu: Some(CatalogGpu {
673 gpu_type: "nvidia-t4",
674 count: 1,
675 }),
676 },
677 InstanceTypeSpec {
679 name: "a2-highgpu-1g",
680 platform: Platform::Gcp,
681 family: InstanceFamily::GpuCompute,
682 architecture: Architecture::X86_64,
683 vcpu: 12,
684 memory_bytes: 85 * GI,
685 ephemeral_storage_bytes: 100 * GI,
686 gpu: Some(CatalogGpu {
687 gpu_type: "nvidia-a100",
688 count: 1,
689 }),
690 },
691 InstanceTypeSpec {
692 name: "a2-highgpu-8g",
693 platform: Platform::Gcp,
694 family: InstanceFamily::GpuCompute,
695 architecture: Architecture::X86_64,
696 vcpu: 96,
697 memory_bytes: 1360 * GI,
698 ephemeral_storage_bytes: 100 * GI,
699 gpu: Some(CatalogGpu {
700 gpu_type: "nvidia-a100",
701 count: 8,
702 }),
703 },
704 InstanceTypeSpec {
706 name: "a3-highgpu-8g",
707 platform: Platform::Gcp,
708 family: InstanceFamily::GpuCompute,
709 architecture: Architecture::X86_64,
710 vcpu: 208,
711 memory_bytes: 1872 * GI,
712 ephemeral_storage_bytes: 100 * GI,
713 gpu: Some(CatalogGpu {
714 gpu_type: "nvidia-h100",
715 count: 8,
716 }),
717 },
718 InstanceTypeSpec {
724 name: "Standard_B1s",
725 platform: Platform::Azure,
726 family: InstanceFamily::Burstable,
727 architecture: Architecture::X86_64,
728 vcpu: 1,
729 memory_bytes: 1 * GI,
730 ephemeral_storage_bytes: 20 * GI,
731 gpu: None,
732 },
733 InstanceTypeSpec {
734 name: "Standard_B2s",
735 platform: Platform::Azure,
736 family: InstanceFamily::Burstable,
737 architecture: Architecture::X86_64,
738 vcpu: 2,
739 memory_bytes: 4 * GI,
740 ephemeral_storage_bytes: 20 * GI,
741 gpu: None,
742 },
743 InstanceTypeSpec {
744 name: "Standard_B2ms",
745 platform: Platform::Azure,
746 family: InstanceFamily::Burstable,
747 architecture: Architecture::X86_64,
748 vcpu: 2,
749 memory_bytes: 8 * GI,
750 ephemeral_storage_bytes: 20 * GI,
751 gpu: None,
752 },
753 InstanceTypeSpec {
754 name: "Standard_B4ms",
755 platform: Platform::Azure,
756 family: InstanceFamily::Burstable,
757 architecture: Architecture::X86_64,
758 vcpu: 4,
759 memory_bytes: 16 * GI,
760 ephemeral_storage_bytes: 20 * GI,
761 gpu: None,
762 },
763 InstanceTypeSpec {
765 name: "Standard_D2s_v5",
766 platform: Platform::Azure,
767 family: InstanceFamily::GeneralPurpose,
768 architecture: Architecture::X86_64,
769 vcpu: 2,
770 memory_bytes: 8 * GI,
771 ephemeral_storage_bytes: 20 * GI,
772 gpu: None,
773 },
774 InstanceTypeSpec {
775 name: "Standard_D4s_v5",
776 platform: Platform::Azure,
777 family: InstanceFamily::GeneralPurpose,
778 architecture: Architecture::X86_64,
779 vcpu: 4,
780 memory_bytes: 16 * GI,
781 ephemeral_storage_bytes: 20 * GI,
782 gpu: None,
783 },
784 InstanceTypeSpec {
785 name: "Standard_D8s_v5",
786 platform: Platform::Azure,
787 family: InstanceFamily::GeneralPurpose,
788 architecture: Architecture::X86_64,
789 vcpu: 8,
790 memory_bytes: 32 * GI,
791 ephemeral_storage_bytes: 20 * GI,
792 gpu: None,
793 },
794 InstanceTypeSpec {
795 name: "Standard_D16s_v5",
796 platform: Platform::Azure,
797 family: InstanceFamily::GeneralPurpose,
798 architecture: Architecture::X86_64,
799 vcpu: 16,
800 memory_bytes: 64 * GI,
801 ephemeral_storage_bytes: 20 * GI,
802 gpu: None,
803 },
804 InstanceTypeSpec {
806 name: "Standard_F2s_v2",
807 platform: Platform::Azure,
808 family: InstanceFamily::ComputeOptimized,
809 architecture: Architecture::X86_64,
810 vcpu: 2,
811 memory_bytes: 4 * GI,
812 ephemeral_storage_bytes: 20 * GI,
813 gpu: None,
814 },
815 InstanceTypeSpec {
816 name: "Standard_F4s_v2",
817 platform: Platform::Azure,
818 family: InstanceFamily::ComputeOptimized,
819 architecture: Architecture::X86_64,
820 vcpu: 4,
821 memory_bytes: 8 * GI,
822 ephemeral_storage_bytes: 20 * GI,
823 gpu: None,
824 },
825 InstanceTypeSpec {
826 name: "Standard_F8s_v2",
827 platform: Platform::Azure,
828 family: InstanceFamily::ComputeOptimized,
829 architecture: Architecture::X86_64,
830 vcpu: 8,
831 memory_bytes: 16 * GI,
832 ephemeral_storage_bytes: 20 * GI,
833 gpu: None,
834 },
835 InstanceTypeSpec {
836 name: "Standard_F16s_v2",
837 platform: Platform::Azure,
838 family: InstanceFamily::ComputeOptimized,
839 architecture: Architecture::X86_64,
840 vcpu: 16,
841 memory_bytes: 32 * GI,
842 ephemeral_storage_bytes: 20 * GI,
843 gpu: None,
844 },
845 InstanceTypeSpec {
847 name: "Standard_E2s_v5",
848 platform: Platform::Azure,
849 family: InstanceFamily::MemoryOptimized,
850 architecture: Architecture::X86_64,
851 vcpu: 2,
852 memory_bytes: 16 * GI,
853 ephemeral_storage_bytes: 20 * GI,
854 gpu: None,
855 },
856 InstanceTypeSpec {
857 name: "Standard_E4s_v5",
858 platform: Platform::Azure,
859 family: InstanceFamily::MemoryOptimized,
860 architecture: Architecture::X86_64,
861 vcpu: 4,
862 memory_bytes: 32 * GI,
863 ephemeral_storage_bytes: 20 * GI,
864 gpu: None,
865 },
866 InstanceTypeSpec {
867 name: "Standard_E8s_v5",
868 platform: Platform::Azure,
869 family: InstanceFamily::MemoryOptimized,
870 architecture: Architecture::X86_64,
871 vcpu: 8,
872 memory_bytes: 64 * GI,
873 ephemeral_storage_bytes: 20 * GI,
874 gpu: None,
875 },
876 InstanceTypeSpec {
877 name: "Standard_E16s_v5",
878 platform: Platform::Azure,
879 family: InstanceFamily::MemoryOptimized,
880 architecture: Architecture::X86_64,
881 vcpu: 16,
882 memory_bytes: 128 * GI,
883 ephemeral_storage_bytes: 20 * GI,
884 gpu: None,
885 },
886 InstanceTypeSpec {
888 name: "Standard_L8s_v3",
889 platform: Platform::Azure,
890 family: InstanceFamily::StorageOptimized,
891 architecture: Architecture::X86_64,
892 vcpu: 8,
893 memory_bytes: 64 * GI,
894 ephemeral_storage_bytes: 1788 * GI,
895 gpu: None,
896 },
897 InstanceTypeSpec {
898 name: "Standard_L16s_v3",
899 platform: Platform::Azure,
900 family: InstanceFamily::StorageOptimized,
901 architecture: Architecture::X86_64,
902 vcpu: 16,
903 memory_bytes: 128 * GI,
904 ephemeral_storage_bytes: 3576 * GI,
905 gpu: None,
906 },
907 InstanceTypeSpec {
908 name: "Standard_L32s_v3",
909 platform: Platform::Azure,
910 family: InstanceFamily::StorageOptimized,
911 architecture: Architecture::X86_64,
912 vcpu: 32,
913 memory_bytes: 256 * GI,
914 ephemeral_storage_bytes: 7154 * GI,
915 gpu: None,
916 },
917 InstanceTypeSpec {
919 name: "Standard_NC4as_T4_v3",
920 platform: Platform::Azure,
921 family: InstanceFamily::GpuCompute,
922 architecture: Architecture::X86_64,
923 vcpu: 4,
924 memory_bytes: 28 * GI,
925 ephemeral_storage_bytes: 176 * GI,
926 gpu: Some(CatalogGpu {
927 gpu_type: "nvidia-t4",
928 count: 1,
929 }),
930 },
931 InstanceTypeSpec {
933 name: "Standard_NC24ads_A100_v4",
934 platform: Platform::Azure,
935 family: InstanceFamily::GpuCompute,
936 architecture: Architecture::X86_64,
937 vcpu: 24,
938 memory_bytes: 220 * GI,
939 ephemeral_storage_bytes: 958 * GI,
940 gpu: Some(CatalogGpu {
941 gpu_type: "nvidia-a100",
942 count: 1,
943 }),
944 },
945 InstanceTypeSpec {
946 name: "Standard_NC96ads_A100_v4",
947 platform: Platform::Azure,
948 family: InstanceFamily::GpuCompute,
949 architecture: Architecture::X86_64,
950 vcpu: 96,
951 memory_bytes: 880 * GI,
952 ephemeral_storage_bytes: 3916 * GI,
953 gpu: Some(CatalogGpu {
954 gpu_type: "nvidia-a100",
955 count: 4,
956 }),
957 },
958 InstanceTypeSpec {
960 name: "Standard_ND96isr_H100_v5",
961 platform: Platform::Azure,
962 family: InstanceFamily::GpuCompute,
963 architecture: Architecture::X86_64,
964 vcpu: 96,
965 memory_bytes: 1900 * GI,
966 ephemeral_storage_bytes: 1000 * GI,
967 gpu: Some(CatalogGpu {
968 gpu_type: "nvidia-h100",
969 count: 8,
970 }),
971 },
972];
973
974pub fn catalog_for_platform(platform: Platform) -> Vec<&'static InstanceTypeSpec> {
980 CATALOG
981 .iter()
982 .filter(|spec| spec.platform == platform)
983 .collect()
984}
985
986pub fn find_instance_type(platform: Platform, name: &str) -> Option<&'static InstanceTypeSpec> {
988 CATALOG
989 .iter()
990 .find(|spec| spec.platform == platform && spec.name == name)
991}
992
993#[derive(Debug, Clone)]
999pub struct WorkloadRequirements {
1000 pub total_cpu_at_max: f64,
1002 pub total_memory_bytes_at_max: u64,
1004 pub max_cpu_per_container: f64,
1006 pub max_memory_per_container: u64,
1008 pub max_ephemeral_storage_bytes: u64,
1010 pub gpu: Option<GpuSpec>,
1012}
1013
1014#[derive(Debug, Clone)]
1016pub struct InstanceSelection {
1017 pub instance_type: &'static str,
1019 pub profile: MachineProfile,
1021 pub min_machines: u32,
1023 pub max_machines: u32,
1025}
1026
1027const STORAGE_OPTIMIZED_THRESHOLD: u64 = 200 * GI;
1029
1030const MAX_MACHINES_PER_CLUSTER: u32 = 10;
1032
1033const MAX_STANDARD_VCPU: u32 = 8;
1036
1037const CONTAINERS_PER_MACHINE: f64 = 4.0;
1039
1040const OVERHEAD_FACTOR: f64 = 1.25;
1042
1043pub fn select_instance_type(
1054 platform: Platform,
1055 requirements: &WorkloadRequirements,
1056) -> Result<InstanceSelection, String> {
1057 let family = select_family(requirements);
1059
1060 let candidates: Vec<&InstanceTypeSpec> = CATALOG
1062 .iter()
1063 .filter(|spec| spec.platform == platform && spec.family == family)
1064 .collect();
1065
1066 if candidates.is_empty() {
1067 return Err(format!(
1068 "no {family:?} instance types in catalog for platform {platform}"
1069 ));
1070 }
1071
1072 let candidates = if let Some(ref gpu) = requirements.gpu {
1074 let filtered: Vec<&InstanceTypeSpec> = candidates
1075 .into_iter()
1076 .filter(|spec| {
1077 spec.gpu.as_ref().map_or(false, |g| {
1078 g.gpu_type == gpu.gpu_type && g.count >= gpu.count
1079 })
1080 })
1081 .collect();
1082 if filtered.is_empty() {
1083 return Err(format!(
1084 "no instance type for GPU type '{}' x{} on platform {platform}",
1085 gpu.gpu_type, gpu.count
1086 ));
1087 }
1088 filtered
1089 } else {
1090 candidates
1091 };
1092
1093 let candidates = if family == InstanceFamily::StorageOptimized {
1095 let filtered: Vec<&InstanceTypeSpec> = candidates
1096 .into_iter()
1097 .filter(|spec| spec.ephemeral_storage_bytes >= requirements.max_ephemeral_storage_bytes)
1098 .collect();
1099 if filtered.is_empty() {
1100 return Err(format!(
1101 "no storage-optimized instance with >= {} bytes ephemeral storage on platform {platform}",
1102 requirements.max_ephemeral_storage_bytes
1103 ));
1104 }
1105 filtered
1106 } else {
1107 candidates
1108 };
1109
1110 let target_cpu =
1113 (requirements.max_cpu_per_container * CONTAINERS_PER_MACHINE * OVERHEAD_FACTOR).max(0.25);
1114 let target_memory =
1115 (requirements.max_memory_per_container as f64 * CONTAINERS_PER_MACHINE * OVERHEAD_FACTOR)
1116 .max(256.0 * MI as f64);
1117
1118 let vcpu_cap =
1120 if family == InstanceFamily::GpuCompute || family == InstanceFamily::StorageOptimized {
1121 u32::MAX
1122 } else {
1123 MAX_STANDARD_VCPU
1124 };
1125
1126 let selected = candidates
1128 .iter()
1129 .filter(|spec| {
1130 spec.vcpu <= vcpu_cap
1131 && spec.vcpu as f64 >= target_cpu
1132 && spec.memory_bytes as f64 >= target_memory
1133 })
1134 .min_by_key(|spec| spec.vcpu)
1135 .or_else(|| {
1136 candidates
1138 .iter()
1139 .filter(|spec| spec.vcpu <= vcpu_cap)
1140 .max_by_key(|spec| spec.vcpu)
1141 })
1142 .or_else(|| {
1143 candidates.iter().min_by_key(|spec| spec.vcpu)
1145 })
1146 .ok_or_else(|| format!("no instance types available for platform {platform}"))?;
1147
1148 let max_machines = compute_max_machines(requirements, selected);
1150 let min_machines = compute_min_machines(max_machines);
1151
1152 Ok(InstanceSelection {
1153 instance_type: selected.name,
1154 profile: selected.to_machine_profile(),
1155 min_machines,
1156 max_machines,
1157 })
1158}
1159
1160pub fn select_family(requirements: &WorkloadRequirements) -> InstanceFamily {
1166 if requirements.gpu.is_some() {
1168 return InstanceFamily::GpuCompute;
1169 }
1170
1171 if requirements.max_ephemeral_storage_bytes > STORAGE_OPTIMIZED_THRESHOLD {
1173 return InstanceFamily::StorageOptimized;
1174 }
1175
1176 if requirements.total_cpu_at_max < 2.0 {
1178 return InstanceFamily::Burstable;
1179 }
1180
1181 InstanceFamily::GeneralPurpose
1183}
1184
1185fn compute_max_machines(requirements: &WorkloadRequirements, instance: &InstanceTypeSpec) -> u32 {
1187 let cpu_with_headroom = requirements.total_cpu_at_max * 1.25;
1189 let cpu_machines = (cpu_with_headroom / instance.vcpu as f64).ceil() as u32;
1190
1191 let mem_with_headroom = requirements.total_memory_bytes_at_max as f64 * 1.25;
1193 let mem_machines = (mem_with_headroom / instance.memory_bytes as f64).ceil() as u32;
1194
1195 cpu_machines
1197 .max(mem_machines)
1198 .max(1)
1199 .min(MAX_MACHINES_PER_CLUSTER)
1200}
1201
1202fn compute_min_machines(max_machines: u32) -> u32 {
1204 if max_machines >= 3 {
1206 2
1207 } else {
1208 1
1209 }
1210}
1211
1212#[cfg(test)]
1217mod tests {
1218 use super::*;
1219
1220 #[test]
1223 fn test_parse_cpu_plain() {
1224 assert_eq!(parse_cpu("1").unwrap(), 1.0);
1225 assert_eq!(parse_cpu("0.5").unwrap(), 0.5);
1226 assert_eq!(parse_cpu("2.0").unwrap(), 2.0);
1227 assert_eq!(parse_cpu("16").unwrap(), 16.0);
1228 }
1229
1230 #[test]
1231 fn test_parse_cpu_millicore() {
1232 assert_eq!(parse_cpu("500m").unwrap(), 0.5);
1233 assert_eq!(parse_cpu("250m").unwrap(), 0.25);
1234 assert_eq!(parse_cpu("1000m").unwrap(), 1.0);
1235 assert_eq!(parse_cpu("100m").unwrap(), 0.1);
1236 }
1237
1238 #[test]
1239 fn test_parse_cpu_invalid() {
1240 assert!(parse_cpu("").is_err());
1241 assert!(parse_cpu("abc").is_err());
1242 assert!(parse_cpu("m").is_err());
1243 }
1244
1245 #[test]
1246 fn test_parse_memory_binary_suffixes() {
1247 assert_eq!(parse_memory_bytes("1Ki").unwrap(), 1024);
1248 assert_eq!(parse_memory_bytes("1Mi").unwrap(), 1024 * 1024);
1249 assert_eq!(parse_memory_bytes("1Gi").unwrap(), 1024 * 1024 * 1024);
1250 assert_eq!(parse_memory_bytes("4Gi").unwrap(), 4 * 1024 * 1024 * 1024);
1251 assert_eq!(parse_memory_bytes("512Mi").unwrap(), 512 * 1024 * 1024);
1252 assert_eq!(
1253 parse_memory_bytes("1Ti").unwrap(),
1254 1024u64 * 1024 * 1024 * 1024
1255 );
1256 }
1257
1258 #[test]
1259 fn test_parse_memory_decimal_suffixes() {
1260 assert_eq!(parse_memory_bytes("1k").unwrap(), 1000);
1261 assert_eq!(parse_memory_bytes("1M").unwrap(), 1_000_000);
1262 assert_eq!(parse_memory_bytes("1G").unwrap(), 1_000_000_000);
1263 assert_eq!(parse_memory_bytes("1T").unwrap(), 1_000_000_000_000);
1264 }
1265
1266 #[test]
1267 fn test_parse_memory_plain_bytes() {
1268 assert_eq!(parse_memory_bytes("1024").unwrap(), 1024);
1269 assert_eq!(parse_memory_bytes("0").unwrap(), 0);
1270 }
1271
1272 #[test]
1273 fn test_parse_memory_invalid() {
1274 assert!(parse_memory_bytes("").is_err());
1275 assert!(parse_memory_bytes("abc").is_err());
1276 assert!(parse_memory_bytes("Gi").is_err());
1277 }
1278
1279 #[test]
1280 fn test_parse_memory_fractional() {
1281 assert_eq!(parse_memory_bytes("0.5Gi").unwrap(), GI / 2);
1282 assert_eq!(parse_memory_bytes("1.5Gi").unwrap(), GI + GI / 2);
1283 }
1284
1285 #[test]
1288 fn test_catalog_has_entries_for_all_cloud_platforms() {
1289 assert!(!catalog_for_platform(Platform::Aws).is_empty());
1290 assert!(!catalog_for_platform(Platform::Gcp).is_empty());
1291 assert!(!catalog_for_platform(Platform::Azure).is_empty());
1292 }
1293
1294 #[test]
1295 fn test_catalog_no_entries_for_non_cloud_platforms() {
1296 assert!(catalog_for_platform(Platform::Local).is_empty());
1297 assert!(catalog_for_platform(Platform::Kubernetes).is_empty());
1298 }
1299
1300 #[test]
1301 fn test_find_known_instance_type() {
1302 let spec =
1303 find_instance_type(Platform::Aws, "m7g.2xlarge").expect("should find m7g.2xlarge");
1304 assert_eq!(spec.vcpu, 8);
1305 assert_eq!(spec.memory_bytes, 32 * GI);
1306 assert_eq!(spec.family, InstanceFamily::GeneralPurpose);
1307 }
1308
1309 #[test]
1310 fn test_find_unknown_instance_type() {
1311 assert!(find_instance_type(Platform::Aws, "nonexistent.xlarge").is_none());
1312 }
1313
1314 #[test]
1315 fn test_find_wrong_platform() {
1316 assert!(find_instance_type(Platform::Gcp, "m7g.2xlarge").is_none());
1317 }
1318
1319 #[test]
1320 fn test_to_machine_profile() {
1321 let spec = find_instance_type(Platform::Aws, "m7g.2xlarge").unwrap();
1322 let profile = spec.to_machine_profile();
1323 assert_eq!(profile.cpu, "8.0");
1324 assert_eq!(profile.memory_bytes, 32 * GI);
1325 assert_eq!(profile.ephemeral_storage_bytes, 20 * GI);
1326 assert!(profile.gpu.is_none());
1327 }
1328
1329 #[test]
1330 fn test_to_machine_profile_with_gpu() {
1331 let spec = find_instance_type(Platform::Aws, "p4d.24xlarge").unwrap();
1332 let profile = spec.to_machine_profile();
1333 let gpu = profile.gpu.as_ref().expect("should have GPU");
1334 assert_eq!(gpu.gpu_type, "nvidia-a100");
1335 assert_eq!(gpu.count, 8);
1336 }
1337
1338 #[test]
1341 fn test_select_burstable_for_small_workload() {
1342 let req = WorkloadRequirements {
1343 total_cpu_at_max: 1.0,
1344 total_memory_bytes_at_max: 2 * GI,
1345 max_cpu_per_container: 0.5,
1346 max_memory_per_container: 1 * GI,
1347 max_ephemeral_storage_bytes: 10 * GI,
1348 gpu: None,
1349 };
1350 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1351 let spec = find_instance_type(Platform::Aws, sel.instance_type).unwrap();
1352 assert_eq!(spec.family, InstanceFamily::Burstable);
1353 }
1354
1355 #[test]
1356 fn test_select_general_purpose_for_standard_workload() {
1357 let req = WorkloadRequirements {
1359 total_cpu_at_max: 20.0,
1360 total_memory_bytes_at_max: 80 * GI,
1361 max_cpu_per_container: 2.0,
1362 max_memory_per_container: 8 * GI,
1363 max_ephemeral_storage_bytes: 10 * GI,
1364 gpu: None,
1365 };
1366 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1367 let spec = find_instance_type(Platform::Aws, sel.instance_type).unwrap();
1368 assert_eq!(spec.family, InstanceFamily::GeneralPurpose);
1369 }
1370
1371 #[test]
1372 fn test_select_general_purpose_even_for_cpu_heavy() {
1373 let req = WorkloadRequirements {
1375 total_cpu_at_max: 20.0,
1376 total_memory_bytes_at_max: 20 * GI,
1377 max_cpu_per_container: 2.0,
1378 max_memory_per_container: 2 * GI,
1379 max_ephemeral_storage_bytes: 10 * GI,
1380 gpu: None,
1381 };
1382 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1383 let spec = find_instance_type(Platform::Aws, sel.instance_type).unwrap();
1384 assert_eq!(spec.family, InstanceFamily::GeneralPurpose);
1385 }
1386
1387 #[test]
1388 fn test_select_storage_optimized_for_large_ephemeral() {
1389 let req = WorkloadRequirements {
1390 total_cpu_at_max: 8.0,
1391 total_memory_bytes_at_max: 32 * GI,
1392 max_cpu_per_container: 2.0,
1393 max_memory_per_container: 8 * GI,
1394 max_ephemeral_storage_bytes: 500 * GI,
1395 gpu: None,
1396 };
1397 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1398 let spec = find_instance_type(Platform::Aws, sel.instance_type).unwrap();
1399 assert_eq!(spec.family, InstanceFamily::StorageOptimized);
1400 }
1401
1402 #[test]
1403 fn test_select_gpu_instance() {
1404 let req = WorkloadRequirements {
1405 total_cpu_at_max: 8.0,
1406 total_memory_bytes_at_max: 32 * GI,
1407 max_cpu_per_container: 4.0,
1408 max_memory_per_container: 16 * GI,
1409 max_ephemeral_storage_bytes: 10 * GI,
1410 gpu: Some(GpuSpec {
1411 gpu_type: "nvidia-a100".to_string(),
1412 count: 1,
1413 }),
1414 };
1415 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1416 let spec = find_instance_type(Platform::Aws, sel.instance_type).unwrap();
1417 assert_eq!(spec.family, InstanceFamily::GpuCompute);
1418 assert!(spec.gpu.is_some());
1419 }
1420
1421 #[test]
1422 fn test_select_works_for_all_cloud_platforms() {
1423 let req = WorkloadRequirements {
1424 total_cpu_at_max: 4.0,
1425 total_memory_bytes_at_max: 16 * GI,
1426 max_cpu_per_container: 1.0,
1427 max_memory_per_container: 4 * GI,
1428 max_ephemeral_storage_bytes: 10 * GI,
1429 gpu: None,
1430 };
1431 for platform in [Platform::Aws, Platform::Gcp, Platform::Azure] {
1432 let sel = select_instance_type(platform, &req);
1433 assert!(sel.is_ok(), "selection failed for {platform}");
1434 }
1435 }
1436
1437 #[test]
1438 fn test_machine_count_reasonable() {
1439 let req = WorkloadRequirements {
1441 total_cpu_at_max: 20.0,
1442 total_memory_bytes_at_max: 40 * GI,
1443 max_cpu_per_container: 1.0,
1444 max_memory_per_container: 2 * GI,
1445 max_ephemeral_storage_bytes: 10 * GI,
1446 gpu: None,
1447 };
1448 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1449 assert!(sel.min_machines >= 1);
1450 assert!(sel.max_machines <= MAX_MACHINES_PER_CLUSTER);
1451 assert!(sel.max_machines >= sel.min_machines);
1452 }
1453
1454 #[test]
1455 fn test_instance_size_capped_at_8_vcpu() {
1456 let req = WorkloadRequirements {
1458 total_cpu_at_max: 70.0,
1459 total_memory_bytes_at_max: 140 * GI,
1460 max_cpu_per_container: 2.0,
1461 max_memory_per_container: 4 * GI,
1462 max_ephemeral_storage_bytes: 10 * GI,
1463 gpu: None,
1464 };
1465 let sel = select_instance_type(Platform::Gcp, &req).unwrap();
1466 let spec = find_instance_type(Platform::Gcp, sel.instance_type).unwrap();
1467 assert!(
1468 spec.vcpu <= MAX_STANDARD_VCPU,
1469 "selected {} with {} vCPUs, expected <= {}",
1470 spec.name,
1471 spec.vcpu,
1472 MAX_STANDARD_VCPU
1473 );
1474 assert_eq!(spec.family, InstanceFamily::GeneralPurpose);
1475 assert!(sel.max_machines > 1);
1477 }
1478
1479 #[test]
1480 fn test_agent_manager_stack_gets_reasonable_instance() {
1481 let req = WorkloadRequirements {
1484 total_cpu_at_max: 70.0, total_memory_bytes_at_max: 140 * GI, max_cpu_per_container: 2.0,
1487 max_memory_per_container: 4 * GI,
1488 max_ephemeral_storage_bytes: 20 * GI,
1489 gpu: None,
1490 };
1491 let sel = select_instance_type(Platform::Gcp, &req).unwrap();
1492 assert_eq!(sel.instance_type, "n2-standard-8");
1494 assert!(sel.max_machines >= 2);
1495 }
1496
1497 #[test]
1498 fn test_profile_has_required_fields() {
1499 let req = WorkloadRequirements {
1500 total_cpu_at_max: 4.0,
1501 total_memory_bytes_at_max: 16 * GI,
1502 max_cpu_per_container: 1.0,
1503 max_memory_per_container: 4 * GI,
1504 max_ephemeral_storage_bytes: 10 * GI,
1505 gpu: None,
1506 };
1507 let sel = select_instance_type(Platform::Aws, &req).unwrap();
1508 assert!(!sel.profile.cpu.is_empty());
1509 assert!(sel.profile.memory_bytes > 0);
1510 assert!(sel.profile.ephemeral_storage_bytes > 0);
1511 }
1512
1513 #[test]
1514 fn test_error_for_unsupported_gpu_type() {
1515 let req = WorkloadRequirements {
1516 total_cpu_at_max: 8.0,
1517 total_memory_bytes_at_max: 32 * GI,
1518 max_cpu_per_container: 4.0,
1519 max_memory_per_container: 16 * GI,
1520 max_ephemeral_storage_bytes: 10 * GI,
1521 gpu: Some(GpuSpec {
1522 gpu_type: "amd-mi300".to_string(),
1523 count: 1,
1524 }),
1525 };
1526 let result = select_instance_type(Platform::Aws, &req);
1527 assert!(result.is_err());
1528 }
1529
1530 #[test]
1531 fn test_catalog_instance_types_sorted_by_vcpu_within_family() {
1532 for platform in [Platform::Aws, Platform::Gcp, Platform::Azure] {
1535 let entries = catalog_for_platform(platform);
1536 let mut by_family: std::collections::HashMap<_, Vec<_>> =
1537 std::collections::HashMap::new();
1538 for entry in entries {
1539 by_family
1540 .entry(format!("{:?}", entry.family))
1541 .or_default()
1542 .push(entry);
1543 }
1544 for (family, instances) in &by_family {
1545 for window in instances.windows(2) {
1546 assert!(
1547 window[0].vcpu <= window[1].vcpu,
1548 "catalog not sorted by vcpu for {platform}/{family}: {} ({}) > {} ({})",
1549 window[0].name,
1550 window[0].vcpu,
1551 window[1].name,
1552 window[1].vcpu
1553 );
1554 }
1555 }
1556 }
1557 }
1558}