diff --git a/app/api/util.ts b/app/api/util.ts index ea227eb65..fea182b7b 100644 --- a/app/api/util.ts +++ b/app/api/util.ts @@ -23,13 +23,13 @@ import type { VpcFirewallRuleUpdate, } from './__generated__/Api' -// API limits encoded in https://github.com/oxidecomputer/omicron/blob/main/nexus/src/app/mod.rs +// API limits encoded in https://github.com/oxidecomputer/omicron/blob/b7af5f8e/nexus/src/app/mod.rs export const MAX_NICS_PER_INSTANCE = 8 export const INSTANCE_MAX_CPU = 64 export const INSTANCE_MIN_RAM_GiB = 1 -export const INSTANCE_MAX_RAM_GiB = 256 +export const INSTANCE_MAX_RAM_GiB = 1024 export const MIN_DISK_SIZE_GiB = 1 /** diff --git a/app/forms/instance-create.tsx b/app/forms/instance-create.tsx index ca4e39efa..7c0bf3f58 100644 --- a/app/forms/instance-create.tsx +++ b/app/forms/instance-create.tsx @@ -432,14 +432,13 @@ export default function CreateInstanceForm() { label="CPUs" name="ncpus" min={1} - max={INSTANCE_MAX_CPU} control={control} validate={(cpus) => { if (cpus < 1) { return `Must be at least 1 vCPU` } if (cpus > INSTANCE_MAX_CPU) { - return `CPUs capped to ${INSTANCE_MAX_CPU}` + return `Can be at most ${INSTANCE_MAX_CPU}` } }} disabled={isSubmitting} @@ -450,7 +449,6 @@ export default function CreateInstanceForm() { label="Memory" name="memory" min={1} - max={INSTANCE_MAX_RAM_GiB} control={control} validate={(memory) => { if (memory < 1) { diff --git a/app/pages/project/instances/InstancePage.tsx b/app/pages/project/instances/InstancePage.tsx index f04931327..d69c5a5f8 100644 --- a/app/pages/project/instances/InstancePage.tsx +++ b/app/pages/project/instances/InstancePage.tsx @@ -374,7 +374,7 @@ export function ResizeInstanceModal({ return `Must be at least 1 vCPU` } if (cpus > INSTANCE_MAX_CPU) { - return `CPUs capped to ${INSTANCE_MAX_CPU}` + return `Can be at most ${INSTANCE_MAX_CPU}` } // We can show this error and therefore inform the user // of the limit rather than preventing it completely diff --git a/mock-api/msw/handlers.ts b/mock-api/msw/handlers.ts index 54405c318..b1caf43fe 100644 --- a/mock-api/msw/handlers.ts +++ b/mock-api/msw/handlers.ts @@ -412,11 +412,8 @@ export const handlers = makeHandlers({ const instanceId = uuid() - // TODO: These values should ultimately be represented in the schema and - // checked with the generated schema validation code. - if (body.memory > INSTANCE_MAX_RAM_GiB * GiB) { - throw `Memory must be less than ${INSTANCE_MAX_RAM_GiB} GiB` + throw `Memory can be at most ${INSTANCE_MAX_RAM_GiB} GiB` } if (body.memory < INSTANCE_MIN_RAM_GiB * GiB) { diff --git a/test/e2e/instance-create.e2e.ts b/test/e2e/instance-create.e2e.ts index 8d0cd614a..c93ce63d4 100644 --- a/test/e2e/instance-create.e2e.ts +++ b/test/e2e/instance-create.e2e.ts @@ -613,3 +613,37 @@ test('create instance with additional disks', async ({ page }) => { await expectRowVisible(otherDisksTable, { Disk: 'new-disk-1', size: '5 GiB' }) await expectRowVisible(otherDisksTable, { Disk: 'disk-3', size: '6 GiB' }) }) + +test('Validate CPU and RAM', async ({ page }) => { + await page.goto('/projects/mock-project/instances-new') + + await page.getByRole('textbox', { name: 'Name', exact: true }).fill('db2') + await selectASiloImage(page, 'ubuntu-22-04') + + await page.getByRole('tab', { name: 'Custom' }).click() + + const cpu = page.getByRole('textbox', { name: 'CPU' }) + await cpu.fill('999') + + // blur CPU + const memory = page.getByRole('textbox', { name: 'Memory' }) + await memory.click() + + // make sure it's not clamping the value + await expect(cpu).toHaveValue('999') + + await memory.fill('1025') + + const submitButton = page.getByRole('button', { name: 'Create instance' }) + + const cpuMsg = page.getByText('Can be at most 64').first() + const memMsg = page.getByText('Can be at most 1024 GiB').first() + + await expect(cpuMsg).toBeHidden() + await expect(memMsg).toBeHidden() + + await submitButton.click() + + await expect(cpuMsg).toBeVisible() + await expect(memMsg).toBeVisible() +})