From 9e45d38026a4997d34ce59d8af4b94207bd2b5b8 Mon Sep 17 00:00:00 2001 From: David Crespo Date: Thu, 22 Feb 2024 20:53:15 -0600 Subject: [PATCH] floating IPs come back in instance external IPs response --- app/test/e2e/floating-ip-create.e2e.ts | 25 +++++++++++++++++++++---- libs/api-mocks/external-ip.ts | 16 +++++++++++++--- libs/api-mocks/msw/db.ts | 2 +- libs/api-mocks/msw/handlers.ts | 11 +++++++++-- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/test/e2e/floating-ip-create.e2e.ts b/app/test/e2e/floating-ip-create.e2e.ts index 50198b47b9..dc22edce20 100644 --- a/app/test/e2e/floating-ip-create.e2e.ts +++ b/app/test/e2e/floating-ip-create.e2e.ts @@ -68,19 +68,34 @@ test('can create a Floating IP', async ({ page }) => { }) test('can detach and attach a Floating IP', async ({ page }) => { + // check floating IP is visible on instance detail + await page.goto('/projects/mock-project/instances/db1') + await expect(page.getByText('192.168.64.64')).toBeVisible() + + // now go detach it await page.goto(floatingIpsPage) await expectRowVisible(page.getByRole('table'), { + name: 'cola-float', + ip: '192.168.64.64', 'Attached to instance': 'db1', }) await clickRowAction(page, 'cola-float', 'Detach') await page.getByRole('button', { name: 'Confirm' }).click() - await expectNotVisible(page, ['role=heading[name*="Detach Floating IP"]']) + await expect(page.getByRole('dialog')).toBeHidden() // Since we detached it, we don't expect to see db1 any longer - await expectNotVisible(page, ['text=db1']) + await expect(page.getByRole('cell', { name: 'db1' })).toBeHidden() + + // click back over to instance page (can't use goto because it refreshes) and + // confirm the IP is no longer there either + await page.getByRole('link', { name: 'Instances' }).click() + await page.getByRole('link', { name: 'db1' }).click() + await expect(page.getByRole('heading', { name: 'db1' })).toBeVisible() + await expect(page.getByText('192.168.64.64')).toBeHidden() - // Reattach it to db1 + // Now click back to floating IPs and reattach it to db1 + await page.getByRole('link', { name: 'Floating IPs' }).click() await clickRowAction(page, 'cola-float', 'Attach') await page.getByRole('button', { name: 'Select instance' }).click() await page.getByRole('option', { name: 'db1' }).click() @@ -88,8 +103,10 @@ test('can detach and attach a Floating IP', async ({ page }) => { await page.getByRole('button', { name: 'Attach' }).click() // The dialog should be gone - await expectNotVisible(page, ['role=heading[name*="Attach Floating IP"]']) + await expect(page.getByRole('dialog')).toBeHidden() await expectRowVisible(page.getByRole('table'), { + name: 'cola-float', + ip: '192.168.64.64', 'Attached to instance': 'db1', }) }) diff --git a/libs/api-mocks/external-ip.ts b/libs/api-mocks/external-ip.ts index 09b2f08fd1..99521f7298 100644 --- a/libs/api-mocks/external-ip.ts +++ b/libs/api-mocks/external-ip.ts @@ -10,14 +10,24 @@ import type { ExternalIp } from '@oxide/api' import { instances } from './instance' import type { Json } from './json-type' +/** + * This is a case where we need extra structure beyond the API response. The API + * response for an ephemeral IP does not indicate the instance it's attached to + * (maybe it should, but that's a separate problem), but we need the instance ID + * on each one in order to look up the IPs for a given instance. The floating IP + * arm of the union does have an instance_id field, so that's a point in favor + * of adding it to the ephemeral IP arm. + */ type DbExternalIp = { instance_id: string external_ip: Json } -// TODO: this type represents the API response, but we need to mock more -// structure in order to be able to look up IPs for a particular instance -export const externalIps: DbExternalIp[] = [ +// Note that ExternalIp is a union of types representing ephemeral and floating +// IPs, but we only put the ephemeral ones here. We have a separate table for +// floating IPs analogous to the floating_ip view in Nexus. + +export const ephemeralIps: DbExternalIp[] = [ { instance_id: instances[0].id, external_ip: { diff --git a/libs/api-mocks/msw/db.ts b/libs/api-mocks/msw/db.ts index 5c29f77aa2..e3d831baaf 100644 --- a/libs/api-mocks/msw/db.ts +++ b/libs/api-mocks/msw/db.ts @@ -263,7 +263,7 @@ const initDb = { /** Join table for `users` and `userGroups` */ groupMemberships: [...mock.groupMemberships], images: [...mock.images], - externalIps: [...mock.externalIps], + ephemeralIps: [...mock.ephemeralIps], instances: [...mock.instances], ipPools: [...mock.ipPools], ipPoolSilos: [...mock.ipPoolSilos], diff --git a/libs/api-mocks/msw/handlers.ts b/libs/api-mocks/msw/handlers.ts index 6044df9d0d..e0ad3e46e9 100644 --- a/libs/api-mocks/msw/handlers.ts +++ b/libs/api-mocks/msw/handlers.ts @@ -496,11 +496,18 @@ export const handlers = makeHandlers({ }, instanceExternalIpList({ path, query }) { const instance = lookup.instance({ ...path, ...query }) - const externalIps = db.externalIps + + const ephemeralIps = db.ephemeralIps .filter((eip) => eip.instance_id === instance.id) .map((eip) => eip.external_ip) + + // floating IPs are missing their `kind` field in the DB so we add it + const floatingIps = db.floatingIps + .filter((f) => f.instance_id === instance.id) + .map((f) => ({ kind: 'floating' as const, ...f })) + // endpoint is not paginated. or rather, it's fake paginated - return { items: externalIps } + return { items: [...ephemeralIps, ...floatingIps] } }, instanceNetworkInterfaceList({ query }) { const instance = lookup.instance(query)