| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- import {
- CloudProvider,
- COMPUTE_MAX_IOPS,
- COMPUTE_MAX_THROUGHPUT,
- computeInstanceAddonVariantIdSchema,
- } from 'shared-data'
- import { z } from 'zod'
- import {
- calculateDiskSizeRequiredForIopsWithGp3,
- calculateDiskSizeRequiredForIopsWithIo2,
- calculateIopsRequiredForThroughput,
- calculateMaxIopsAllowedForDiskSizeWithGp3,
- calculateMaxIopsAllowedForDiskSizeWithio2,
- calculateMaxThroughput,
- formatNumber,
- } from './DiskManagement.utils'
- import { DISK_LIMITS, DiskType } from './ui/DiskManagement.constants'
- const baseSchema = z.object({
- storageType: z.enum(['io2', 'gp3']).describe('Type of storage: io2 or gp3'),
- totalSize: z.number().int('Value must be an integer').describe('Allocated disk size in GB'),
- provisionedIOPS: z.number().describe('Provisioned IOPS for storage type'),
- throughput: z.number().optional().describe('Throughput in MB/s for gp3'),
- computeSize: computeInstanceAddonVariantIdSchema
- .describe('Compute size')
- .optional()
- .default('ci_micro'),
- growthPercent: z
- .number()
- .int('Value must be an integer')
- .min(10, 'Growth percent must be at least 10%')
- .max(100, 'Growth percent cannot exceed 100%')
- .optional()
- .nullable(),
- minIncrementGb: z
- .number()
- .int('Value must be an integer')
- .min(1, 'Minimum increment must be at least 1 GB')
- .max(200, 'Minimum increment cannot exceed 200 GB')
- .optional()
- .nullable(),
- maxSizeGb: z
- .number()
- .int('Value must be an integer')
- .max(60000, 'Maximum size cannot exceed 60TB')
- .optional()
- .nullable(),
- })
- export const CreateDiskStorageSchema = ({
- defaultTotalSize,
- cloudProvider,
- isSpendCapEnabled,
- }: {
- defaultTotalSize: number
- cloudProvider: CloudProvider
- isSpendCapEnabled: boolean
- }) => {
- const isFlyProject = cloudProvider === 'FLY'
- const isAwsNimbusProject = cloudProvider === 'AWS_NIMBUS'
- const isAwsK8sProject = cloudProvider === 'AWS_K8S'
- const validateDiskConfiguration = !isFlyProject && !isAwsNimbusProject && !isAwsK8sProject
- const schema = baseSchema.superRefine((data, ctx) => {
- const { storageType, totalSize, provisionedIOPS, throughput, maxSizeGb, computeSize } = data
- const computeMaxIops = (() => {
- const parsedCompute = computeInstanceAddonVariantIdSchema.safeParse(computeSize)
- if (!parsedCompute.success) return Number.POSITIVE_INFINITY
- return COMPUTE_MAX_IOPS[parsedCompute.data] ?? Number.POSITIVE_INFINITY
- })()
- if (validateDiskConfiguration && totalSize < 8 && totalSize !== defaultTotalSize) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: 'New disk size must be at least 8 GB.',
- path: ['totalSize'],
- })
- }
- if (
- validateDiskConfiguration &&
- isSpendCapEnabled &&
- totalSize > 8 &&
- totalSize !== defaultTotalSize
- ) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: 'Disable spend cap to increase disk above 8 GB.',
- path: ['totalSize'],
- })
- }
- if (validateDiskConfiguration && totalSize < defaultTotalSize) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Disk size cannot be reduced in size. Reduce your database size and then head to the Infrastructure settings and go through a Postgres version upgrade to right-size your disk.`,
- path: ['totalSize'],
- })
- }
- // Validate maxSizeGb cannot be lower than totalSize
- if (validateDiskConfiguration && !!maxSizeGb && maxSizeGb < totalSize) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Max disk size cannot be lower than the current disk size. Must be at least ${formatNumber(totalSize)} GB.`,
- path: ['maxSizeGb'],
- })
- }
- if (validateDiskConfiguration && storageType === 'io2') {
- // Validation rules for io2
- const maxIopsForIo2 = Math.min(DISK_LIMITS[DiskType.IO2].maxIops, computeMaxIops)
- if (provisionedIOPS > maxIopsForIo2) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `IOPS cannot exceed ${formatNumber(maxIopsForIo2)} for io2 Disk type and the selected compute size.`,
- path: ['provisionedIOPS'],
- })
- }
- const maxIOPSforDiskSizeWithio2 = calculateMaxIopsAllowedForDiskSizeWithio2(totalSize)
- if (provisionedIOPS < DISK_LIMITS[DiskType.IO2].minIops) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Provisioned IOPS must be at least ${formatNumber(DISK_LIMITS[DiskType.IO2].minIops)}`,
- path: ['provisionedIOPS'],
- })
- } else if (provisionedIOPS > maxIOPSforDiskSizeWithio2) {
- if (totalSize >= 8) {
- const diskSizeRequiredForIopsWithIo2 =
- calculateDiskSizeRequiredForIopsWithIo2(provisionedIOPS)
- if (diskSizeRequiredForIopsWithIo2 > totalSize) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Larger Disk size of at least ${formatNumber(diskSizeRequiredForIopsWithIo2)} GB required. Current max is ${formatNumber(maxIOPSforDiskSizeWithio2)} IOPS.`,
- path: ['provisionedIOPS'],
- })
- }
- } else {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Invalid IOPS value due to small disk size`,
- path: ['provisionedIOPS'],
- })
- }
- }
- if (throughput !== undefined) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: 'Throughput is not configurable for io2.',
- path: ['throughput'],
- })
- }
- if (totalSize > DISK_LIMITS[DiskType.IO2].maxStorage) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Allocated disksize must not exceed ${formatNumber(DISK_LIMITS[DiskType.IO2].maxStorage)} GB `,
- path: ['totalSize'],
- })
- }
- }
- if (validateDiskConfiguration && storageType === 'gp3') {
- const maxIopsAllowedForDiskSizeWithGp3 = calculateMaxIopsAllowedForDiskSizeWithGp3(totalSize)
- const maxIopsForGp3 = Math.min(DISK_LIMITS[DiskType.GP3].maxIops, computeMaxIops)
- const computeMaxThroughput = (() => {
- const parsedCompute = computeInstanceAddonVariantIdSchema.safeParse(computeSize)
- if (!parsedCompute.success) return DISK_LIMITS[DiskType.GP3].maxThroughput
- return COMPUTE_MAX_THROUGHPUT[parsedCompute.data] ?? DISK_LIMITS[DiskType.GP3].maxThroughput
- })()
- if (provisionedIOPS > maxIopsForGp3) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `IOPS cannot exceed ${formatNumber(maxIopsForGp3)} for GP3 Disk and the selected compute size.`,
- path: ['provisionedIOPS'],
- })
- }
- if (provisionedIOPS < DISK_LIMITS[DiskType.GP3].minIops) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `IOPS must be at least ${formatNumber(DISK_LIMITS[DiskType.GP3].minIops)}`,
- path: ['provisionedIOPS'],
- })
- } else if (provisionedIOPS > maxIopsAllowedForDiskSizeWithGp3) {
- const diskSizeRequiredForIopsWithGp3 =
- calculateDiskSizeRequiredForIopsWithGp3(provisionedIOPS)
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Larger Disk size of at least ${formatNumber(diskSizeRequiredForIopsWithGp3)} GB required. Current max is ${formatNumber(maxIopsAllowedForDiskSizeWithGp3)} IOPS.`,
- path: ['provisionedIOPS'],
- })
- }
- if (throughput !== undefined) {
- // gp3 throughput scales with provisioned IOPS (capped by gp3 max)
- const iopsThroughputLimit = calculateMaxThroughput(provisionedIOPS)
- if (throughput > DISK_LIMITS[DiskType.GP3].maxThroughput) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Throughput cannot exceed ${formatNumber(DISK_LIMITS[DiskType.GP3].maxThroughput)} MB/s for GP3 disk type.`,
- path: ['throughput'],
- })
- } else if (throughput > computeMaxThroughput) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Throughput cannot exceed ${formatNumber(computeMaxThroughput)} MB/s for the selected compute size.`,
- path: ['throughput'],
- })
- } else if (throughput > iopsThroughputLimit) {
- const iopsRequiredForThroughput = calculateIopsRequiredForThroughput(throughput)
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Need at least ${formatNumber(iopsRequiredForThroughput)} IOPS to support ${formatNumber(throughput)} MB/s.`,
- path: ['throughput'],
- })
- }
- if (throughput < DISK_LIMITS[DiskType.GP3].minThroughput) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Throughput must be at least ${formatNumber(DISK_LIMITS[DiskType.GP3].minThroughput)} MB/s`,
- path: ['throughput'],
- })
- }
- }
- if (totalSize > DISK_LIMITS[DiskType.GP3].maxStorage) {
- ctx.addIssue({
- code: z.ZodIssueCode.custom,
- message: `Allocated disksize must not exceed ${formatNumber(DISK_LIMITS[DiskType.GP3].maxStorage)} GB`,
- path: ['totalSize'],
- })
- }
- }
- })
- return schema
- }
- export type DiskStorageSchemaType = z.infer<ReturnType<typeof CreateDiskStorageSchema>>
|