Skip to content

Commit cb109cb

Browse files
authored
Return floating IPs from mock instance external IPs endpoint (#1968)
floating IPs come back in instance external IPs response
1 parent 264e2cd commit cb109cb

File tree

4 files changed

+44
-10
lines changed

4 files changed

+44
-10
lines changed

app/test/e2e/floating-ip-create.e2e.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,28 +68,45 @@ test('can create a Floating IP', async ({ page }) => {
6868
})
6969

7070
test('can detach and attach a Floating IP', async ({ page }) => {
71+
// check floating IP is visible on instance detail
72+
await page.goto('/projects/mock-project/instances/db1')
73+
await expect(page.getByText('192.168.64.64')).toBeVisible()
74+
75+
// now go detach it
7176
await page.goto(floatingIpsPage)
7277

7378
await expectRowVisible(page.getByRole('table'), {
79+
name: 'cola-float',
80+
ip: '192.168.64.64',
7481
'Attached to instance': 'db1',
7582
})
7683
await clickRowAction(page, 'cola-float', 'Detach')
7784
await page.getByRole('button', { name: 'Confirm' }).click()
7885

79-
await expectNotVisible(page, ['role=heading[name*="Detach Floating IP"]'])
86+
await expect(page.getByRole('dialog')).toBeHidden()
8087
// Since we detached it, we don't expect to see db1 any longer
81-
await expectNotVisible(page, ['text=db1'])
88+
await expect(page.getByRole('cell', { name: 'db1' })).toBeHidden()
89+
90+
// click back over to instance page (can't use goto because it refreshes) and
91+
// confirm the IP is no longer there either
92+
await page.getByRole('link', { name: 'Instances' }).click()
93+
await page.getByRole('link', { name: 'db1' }).click()
94+
await expect(page.getByRole('heading', { name: 'db1' })).toBeVisible()
95+
await expect(page.getByText('192.168.64.64')).toBeHidden()
8296

83-
// Reattach it to db1
97+
// Now click back to floating IPs and reattach it to db1
98+
await page.getByRole('link', { name: 'Floating IPs' }).click()
8499
await clickRowAction(page, 'cola-float', 'Attach')
85100
await page.getByRole('button', { name: 'Select instance' }).click()
86101
await page.getByRole('option', { name: 'db1' }).click()
87102

88103
await page.getByRole('button', { name: 'Attach' }).click()
89104

90105
// The dialog should be gone
91-
await expectNotVisible(page, ['role=heading[name*="Attach Floating IP"]'])
106+
await expect(page.getByRole('dialog')).toBeHidden()
92107
await expectRowVisible(page.getByRole('table'), {
108+
name: 'cola-float',
109+
ip: '192.168.64.64',
93110
'Attached to instance': 'db1',
94111
})
95112
})

libs/api-mocks/external-ip.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,24 @@ import type { ExternalIp } from '@oxide/api'
1010
import { instances } from './instance'
1111
import type { Json } from './json-type'
1212

13+
/**
14+
* This is a case where we need extra structure beyond the API response. The API
15+
* response for an ephemeral IP does not indicate the instance it's attached to
16+
* (maybe it should, but that's a separate problem), but we need the instance ID
17+
* on each one in order to look up the IPs for a given instance. The floating IP
18+
* arm of the union does have an instance_id field, so that's a point in favor
19+
* of adding it to the ephemeral IP arm.
20+
*/
1321
type DbExternalIp = {
1422
instance_id: string
1523
external_ip: Json<ExternalIp>
1624
}
1725

18-
// TODO: this type represents the API response, but we need to mock more
19-
// structure in order to be able to look up IPs for a particular instance
20-
export const externalIps: DbExternalIp[] = [
26+
// Note that ExternalIp is a union of types representing ephemeral and floating
27+
// IPs, but we only put the ephemeral ones here. We have a separate table for
28+
// floating IPs analogous to the floating_ip view in Nexus.
29+
30+
export const ephemeralIps: DbExternalIp[] = [
2131
{
2232
instance_id: instances[0].id,
2333
external_ip: {

libs/api-mocks/msw/db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ const initDb = {
263263
/** Join table for `users` and `userGroups` */
264264
groupMemberships: [...mock.groupMemberships],
265265
images: [...mock.images],
266-
externalIps: [...mock.externalIps],
266+
ephemeralIps: [...mock.ephemeralIps],
267267
instances: [...mock.instances],
268268
ipPools: [...mock.ipPools],
269269
ipPoolSilos: [...mock.ipPoolSilos],

libs/api-mocks/msw/handlers.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,11 +496,18 @@ export const handlers = makeHandlers({
496496
},
497497
instanceExternalIpList({ path, query }) {
498498
const instance = lookup.instance({ ...path, ...query })
499-
const externalIps = db.externalIps
499+
500+
const ephemeralIps = db.ephemeralIps
500501
.filter((eip) => eip.instance_id === instance.id)
501502
.map((eip) => eip.external_ip)
503+
504+
// floating IPs are missing their `kind` field in the DB so we add it
505+
const floatingIps = db.floatingIps
506+
.filter((f) => f.instance_id === instance.id)
507+
.map((f) => ({ kind: 'floating' as const, ...f }))
508+
502509
// endpoint is not paginated. or rather, it's fake paginated
503-
return { items: externalIps }
510+
return { items: [...ephemeralIps, ...floatingIps] }
504511
},
505512
instanceNetworkInterfaceList({ query }) {
506513
const instance = lookup.instance(query)

0 commit comments

Comments
 (0)