-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathip-pools.e2e.ts
342 lines (264 loc) · 12.9 KB
/
ip-pools.e2e.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* Copyright Oxide Computer Company
*/
import { expect, test } from '@playwright/test'
import { clickRowAction, expectRowVisible, expectToast } from './utils'
test('IP pool list', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
await expect(page).toHaveTitle('IP Pools / Oxide Console')
await expect(page.getByRole('heading', { name: 'IP Pools' })).toBeVisible()
const table = page.getByRole('table')
await expect(table.getByRole('row')).toHaveCount(5) // header + 4 rows
await expectRowVisible(table, { name: 'ip-pool-1', Utilization: '6 / 24' })
await expectRowVisible(table, {
name: 'ip-pool-2',
Utilization: 'v4' + '0 / 0' + 'v6' + '0 / 32',
})
await expectRowVisible(table, { name: 'ip-pool-3', Utilization: '0 / 0' })
await expectRowVisible(table, {
name: 'ip-pool-4',
Utilization: 'v4' + '0 / 207' + 'v6' + '0 / 18.4e18',
})
})
test.describe('german locale', () => {
test.use({ locale: 'de-DE' })
test('IP pools list renders bignum with correct locale', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
const table = page.getByRole('table')
await expectRowVisible(table, {
name: 'ip-pool-4',
Utilization: 'v4' + '0 / 207' + 'v6' + '0 / 18,4e18',
})
})
test('IP pool CapacityBar renders bignum with correct locale', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-4')
await expect(page.getByText('Capacity18,4e18')).toBeVisible()
})
})
test('IP pool silo list', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
await page.getByRole('link', { name: 'ip-pool-1' }).click()
await expect(page).toHaveTitle('ip-pool-1 / IP Pools / Oxide Console')
await page.getByRole('tab', { name: 'Linked silos' }).click()
// this is here because waiting for the `tab` query param to show up avoids
// flake after the goBack bit below
await expect(page).toHaveURL('/system/networking/ip-pools/ip-pool-1?tab=silos')
const table = page.getByRole('table')
await expectRowVisible(table, { Silo: 'maze-war', 'Pool is silo default': 'default' })
// clicking silo takes you to silo page
const siloLink = page.getByRole('link', { name: 'maze-war' })
await siloLink.click()
await expect(page).toHaveURL('/system/silos/maze-war?tab=ip-pools')
await page.goBack()
// unlink silo and the row is gone
await clickRowAction(page, 'maze-war', 'Unlink')
await expect(page.getByRole('dialog', { name: 'Confirm unlink' })).toBeVisible()
await page.getByRole('button', { name: 'Confirm' }).click()
await expect(siloLink).toBeHidden()
})
test('IP pool link silo', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-1?tab=silos')
const table = page.getByRole('table')
await expectRowVisible(table, { Silo: 'maze-war', 'Pool is silo default': 'default' })
await expect(table.getByRole('row')).toHaveCount(2) // header and 1 row
const modal = page.getByRole('dialog', { name: 'Link silo' })
await expect(modal).toBeHidden()
// open link modal
await page.getByRole('button', { name: 'Link silo' }).click()
await expect(modal).toBeVisible()
// close modal works
await page.getByRole('button', { name: 'Cancel' }).click()
await expect(modal).toBeHidden()
// reopen
await page.getByRole('button', { name: 'Link silo' }).click()
await expect(modal).toBeVisible()
// select silo in combobox and click link
await page.getByPlaceholder('Select a silo').fill('m')
await page.getByRole('option', { name: 'myriad' }).click()
await modal.getByRole('button', { name: 'Link' }).click()
// modal closes and we see the thing in the table
await expect(modal).toBeHidden()
await expectRowVisible(table, { Silo: 'myriad', 'Pool is silo default': '' })
})
test('IP pool delete from IP Pools list page', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
// can't delete a pool containing ranges
await clickRowAction(page, 'ip-pool-1', 'Delete')
await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible()
await page.getByRole('button', { name: 'Confirm' }).click()
await expectToast(
page,
'Could not delete resourceIP pool cannot be deleted while it contains IP ranges'
)
await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeVisible()
// can delete a pool with no ranges
await clickRowAction(page, 'ip-pool-3', 'Delete')
await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible()
await page.getByRole('button', { name: 'Confirm' }).click()
await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeHidden()
})
test('IP pool delete from IP Pool view page', async ({ page }) => {
// can't delete a pool containing ranges
await page.goto('/system/networking/ip-pools/ip-pool-1')
await page.getByRole('button', { name: 'IP pool actions' }).click()
await expect(page.getByRole('menuitem', { name: 'Delete' })).toBeDisabled()
// can delete a pool with no ranges
await page.goto('/system/networking/ip-pools/ip-pool-3')
await page.getByRole('button', { name: 'IP pool actions' }).click()
await page.getByRole('menuitem', { name: 'Delete' }).click()
await expect(page.getByRole('dialog', { name: 'Confirm delete' })).toBeVisible()
await page.getByRole('button', { name: 'Confirm' }).click()
// get redirected back to the list after successful delete
await expect(page).toHaveURL('/system/networking/ip-pools')
await expect(page.getByRole('cell', { name: 'ip-pool-3' })).toBeHidden()
})
test('IP pool create', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
await expect(page.getByRole('cell', { name: 'another-pool' })).toBeHidden()
const modal = page.getByRole('dialog', { name: 'Create IP pool' })
await expect(modal).toBeHidden()
await page.getByRole('link', { name: 'New IP pool' }).click()
await expect(modal).toBeVisible()
await page.getByRole('textbox', { name: 'Name' }).fill('another-pool')
await page.getByRole('textbox', { name: 'Description' }).fill('whatever')
await page.getByRole('button', { name: 'Create IP pool' }).click()
await expect(modal).toBeHidden()
await expectRowVisible(page.getByRole('table'), {
name: 'another-pool',
description: 'whatever',
})
})
test('IP pool edit', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-3')
await page.getByRole('button', { name: 'IP pool actions' }).click()
await page.getByRole('menuitem', { name: 'Edit' }).click()
const modal = page.getByRole('dialog', { name: 'Edit IP pool' })
await expect(modal).toBeVisible()
await page.getByRole('textbox', { name: 'Name' }).fill('updated-pool')
await page.getByRole('textbox', { name: 'Description' }).fill('an updated description')
await page.getByRole('button', { name: 'Update IP pool' }).click()
await expect(modal).toBeHidden()
await expect(page).toHaveURL('/system/networking/ip-pools/updated-pool')
await expect(page.getByRole('heading', { name: 'updated-pool' })).toBeVisible()
})
test('IP range validation and add', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-2')
// check the utilization bar
await expect(page.getByText('IPv4(IPs)')).toBeHidden()
await expect(page.getByText('IPv6(IPs)0%')).toBeVisible()
await expect(page.getByText('Allocated0')).toBeVisible()
await expect(page.getByText('Capacity32')).toBeVisible()
await page.getByRole('link', { name: 'Add range' }).click()
const dialog = page.getByRole('dialog', { name: 'Add IP range' })
const first = dialog.getByRole('textbox', { name: 'First' })
const last = dialog.getByRole('textbox', { name: 'Last' })
const submit = dialog.getByRole('button', { name: 'Add IP range' })
const invalidMsg = dialog.getByText('Not a valid IP address')
// exact to differentiate from same text in help message at the top of the form
const ipv6Msg = dialog.getByText('IPv6 ranges are not yet supported')
const v4Addr = '192.1.2.3'
const v6Addr = '2001:db8::1234:5678'
await expect(dialog).toBeVisible()
await first.fill('abc')
// last is empty
await submit.click()
await expect(invalidMsg).toHaveCount(2)
// change last to v6, not allowed
await last.fill(v6Addr)
await expect(invalidMsg).toHaveCount(1)
await expect(ipv6Msg).toHaveCount(1)
// change first to v6, still not allowed
await first.fill(v6Addr)
await expect(ipv6Msg).toHaveCount(2)
await expect(invalidMsg).toBeHidden()
// now make first v4, then last
await first.fill(v4Addr)
await expect(ipv6Msg).toHaveCount(1)
await last.fill(v4Addr)
await expect(ipv6Msg).toBeHidden()
await submit.click()
await expect(dialog).toBeHidden()
const table = page.getByRole('table')
await expectRowVisible(table, { First: v4Addr, Last: v4Addr })
// now the utilization bars are split in two
await expect(page.getByText('IPv4(IPs)0%')).toBeVisible()
await expect(page.getByText('Allocated0')).toHaveCount(2)
await expect(page.getByText('Capacity1')).toBeVisible()
await expect(page.getByText('IPv6(IPs)0%')).toBeVisible()
await expect(page.getByText('Capacity32')).toBeVisible()
// go back to the pool and verify the utilization column changed
// use the sidebar nav to get there
const sidebar = page.getByRole('navigation', { name: 'Sidebar navigation' })
await sidebar.getByRole('link', { name: 'IP Pools' }).click()
await expectRowVisible(table, {
name: 'ip-pool-2',
Utilization: 'v4' + '0 / 1' + 'v6' + '0 / 32',
})
})
test('remove range', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-1')
const table = page.getByRole('table')
await expectRowVisible(table, { First: '10.0.0.20', Last: '10.0.0.22' })
await expect(table.getByRole('row')).toHaveCount(3) // header + 2 rows
await clickRowAction(page, '10.0.0.20', 'Remove')
const confirmModal = page.getByRole('dialog', { name: 'Confirm remove range' })
await expect(confirmModal.getByText('range 10.0.0.20–10.0.0.22')).toBeVisible()
await page.getByRole('button', { name: 'Cancel' }).click()
await expect(confirmModal).toBeHidden()
await clickRowAction(page, '10.0.0.20', 'Remove')
await confirmModal.getByRole('button', { name: 'Confirm' }).click()
await expect(table.getByRole('cell', { name: '10.0.0.20' })).toBeHidden()
await expect(table.getByRole('row')).toHaveCount(2)
// utilization updates
await expect(page.getByText('IPv4(IPs)28.57%')).toBeVisible()
await expect(page.getByText('Allocated6')).toBeVisible()
await expect(page.getByText('Capacity21')).toBeVisible()
// go back to the pool and verify the utilization column changed
// use the topbar breadcrumb to get there
const breadcrumbs = page.getByRole('navigation', { name: 'Breadcrumbs' })
await breadcrumbs.getByRole('link', { name: 'IP Pools' }).click()
await expectRowVisible(table, {
name: 'ip-pool-1',
Utilization: '6 / 21',
})
})
test('deleting floating IP decrements utilization', async ({ page }) => {
await page.goto('/system/networking/ip-pools')
const table = page.getByRole('table')
await expectRowVisible(table, { name: 'ip-pool-1', Utilization: '6 / 24' })
// go delete a floating IP
await page.getByLabel('Switch between system and silo').click()
await page.getByRole('menuitem', { name: 'Silo' }).click()
await page.getByRole('link', { name: 'mock-project' }).click()
await page.getByRole('link', { name: 'Floating IPs' }).click()
await clickRowAction(page, 'rootbeer-float', 'Delete')
await page.getByRole('button', { name: 'Confirm' }).click()
// now go back and it's 5. wow
await page.getByLabel('Switch between system and silo').click()
await page.getByRole('menuitem', { name: 'System' }).click()
await page.getByRole('link', { name: 'IP Pools' }).click()
await expectRowVisible(table, { name: 'ip-pool-1', Utilization: '5 / 24' })
})
test('no ranges means no utilization bar', async ({ page }) => {
await page.goto('/system/networking/ip-pools/ip-pool-1')
await expect(page.getByText('IPv4(IPs)')).toBeVisible()
await expect(page.getByText('IPv6(IPs)')).toBeHidden()
await page.goto('/system/networking/ip-pools/ip-pool-2')
await expect(page.getByText('IPv4(IPs)')).toBeHidden()
await expect(page.getByText('IPv6(IPs)')).toBeVisible()
await page.goto('/system/networking/ip-pools/ip-pool-3')
await expect(page.getByText('IPv4(IPs)')).toBeHidden()
await expect(page.getByText('IPv6(IPs)')).toBeHidden()
await page.goto('/system/networking/ip-pools/ip-pool-4')
await expect(page.getByText('IPv4(IPs)')).toBeVisible()
await expect(page.getByText('IPv6(IPs)')).toBeVisible()
await clickRowAction(page, '10.0.0.50', 'Remove')
const confirmModal = page.getByRole('dialog', { name: 'Confirm remove range' })
await confirmModal.getByRole('button', { name: 'Confirm' }).click()
await expect(page.getByText('IPv4(IPs)')).toBeHidden()
await expect(page.getByText('IPv6(IPs)')).toBeVisible()
})