github.com/hernad/nomad@v1.6.112/ui/tests/acceptance/volume-detail-test.js (about) 1 /** 2 * Copyright (c) HashiCorp, Inc. 3 * SPDX-License-Identifier: MPL-2.0 4 */ 5 6 /* eslint-disable qunit/require-expect */ 7 import { module, test } from 'qunit'; 8 import { currentURL } from '@ember/test-helpers'; 9 import { setupApplicationTest } from 'ember-qunit'; 10 import { setupMirage } from 'ember-cli-mirage/test-support'; 11 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 12 import moment from 'moment'; 13 import { formatBytes, formatHertz } from 'nomad-ui/utils/units'; 14 import VolumeDetail from 'nomad-ui/tests/pages/storage/volumes/detail'; 15 import Layout from 'nomad-ui/tests/pages/layout'; 16 17 const assignWriteAlloc = (volume, alloc) => { 18 volume.writeAllocs.add(alloc); 19 volume.allocations.add(alloc); 20 volume.save(); 21 }; 22 23 const assignReadAlloc = (volume, alloc) => { 24 volume.readAllocs.add(alloc); 25 volume.allocations.add(alloc); 26 volume.save(); 27 }; 28 29 module('Acceptance | volume detail', function (hooks) { 30 setupApplicationTest(hooks); 31 setupMirage(hooks); 32 33 let volume; 34 35 hooks.beforeEach(function () { 36 server.create('node-pool'); 37 server.create('node'); 38 server.create('csi-plugin', { createVolumes: false }); 39 volume = server.create('csi-volume'); 40 }); 41 42 test('it passes an accessibility audit', async function (assert) { 43 await VolumeDetail.visit({ id: `${volume.id}@default` }); 44 await a11yAudit(assert); 45 }); 46 47 test('/csi/volumes/:id should have a breadcrumb trail linking back to Volumes and Storage', async function (assert) { 48 await VolumeDetail.visit({ id: `${volume.id}@default` }); 49 50 assert.equal(Layout.breadcrumbFor('csi.index').text, 'Storage'); 51 assert.equal(Layout.breadcrumbFor('csi.volumes').text, 'Volumes'); 52 assert.equal(Layout.breadcrumbFor('csi.volumes.volume').text, volume.name); 53 }); 54 55 test('/csi/volumes/:id should show the volume name in the title', async function (assert) { 56 await VolumeDetail.visit({ id: `${volume.id}@default` }); 57 58 assert.equal(document.title, `CSI Volume ${volume.name} - Nomad`); 59 assert.equal(VolumeDetail.title, volume.name); 60 }); 61 62 test('/csi/volumes/:id should list additional details for the volume below the title', async function (assert) { 63 await VolumeDetail.visit({ id: `${volume.id}@default` }); 64 65 assert.ok( 66 VolumeDetail.health.includes( 67 volume.schedulable ? 'Schedulable' : 'Unschedulable' 68 ) 69 ); 70 assert.ok(VolumeDetail.provider.includes(volume.provider)); 71 assert.ok(VolumeDetail.externalId.includes(volume.externalId)); 72 assert.notOk( 73 VolumeDetail.hasNamespace, 74 'Namespace is omitted when there is only one namespace' 75 ); 76 }); 77 78 test('/csi/volumes/:id should list all write allocations the volume is attached to', async function (assert) { 79 const writeAllocations = server.createList('allocation', 2); 80 const readAllocations = server.createList('allocation', 3); 81 writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); 82 readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); 83 84 await VolumeDetail.visit({ id: `${volume.id}@default` }); 85 86 assert.equal(VolumeDetail.writeAllocations.length, writeAllocations.length); 87 writeAllocations 88 .sortBy('modifyIndex') 89 .reverse() 90 .forEach((allocation, idx) => { 91 assert.equal( 92 allocation.id, 93 VolumeDetail.writeAllocations.objectAt(idx).id 94 ); 95 }); 96 }); 97 98 test('/csi/volumes/:id should list all read allocations the volume is attached to', async function (assert) { 99 const writeAllocations = server.createList('allocation', 2); 100 const readAllocations = server.createList('allocation', 3); 101 writeAllocations.forEach((alloc) => assignWriteAlloc(volume, alloc)); 102 readAllocations.forEach((alloc) => assignReadAlloc(volume, alloc)); 103 104 await VolumeDetail.visit({ id: `${volume.id}@default` }); 105 106 assert.equal(VolumeDetail.readAllocations.length, readAllocations.length); 107 readAllocations 108 .sortBy('modifyIndex') 109 .reverse() 110 .forEach((allocation, idx) => { 111 assert.equal( 112 allocation.id, 113 VolumeDetail.readAllocations.objectAt(idx).id 114 ); 115 }); 116 }); 117 118 test('each allocation should have high-level details for the allocation', async function (assert) { 119 const allocation = server.create('allocation', { clientStatus: 'running' }); 120 assignWriteAlloc(volume, allocation); 121 122 const allocStats = server.db.clientAllocationStats.find(allocation.id); 123 const taskGroup = server.db.taskGroups.findBy({ 124 name: allocation.taskGroup, 125 jobId: allocation.jobId, 126 }); 127 128 const tasks = taskGroup.taskIds.map((id) => server.db.tasks.find(id)); 129 const cpuUsed = tasks.reduce((sum, task) => sum + task.resources.CPU, 0); 130 const memoryUsed = tasks.reduce( 131 (sum, task) => sum + task.resources.MemoryMB, 132 0 133 ); 134 135 await VolumeDetail.visit({ id: `${volume.id}@default` }); 136 137 VolumeDetail.writeAllocations.objectAt(0).as((allocationRow) => { 138 assert.equal( 139 allocationRow.shortId, 140 allocation.id.split('-')[0], 141 'Allocation short ID' 142 ); 143 assert.equal( 144 allocationRow.createTime, 145 moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), 146 'Allocation create time' 147 ); 148 assert.equal( 149 allocationRow.modifyTime, 150 moment(allocation.modifyTime / 1000000).fromNow(), 151 'Allocation modify time' 152 ); 153 assert.equal( 154 allocationRow.status, 155 allocation.clientStatus, 156 'Client status' 157 ); 158 assert.equal( 159 allocationRow.job, 160 server.db.jobs.find(allocation.jobId).name, 161 'Job name' 162 ); 163 assert.ok(allocationRow.taskGroup, 'Task group name'); 164 assert.ok(allocationRow.jobVersion, 'Job Version'); 165 assert.equal( 166 allocationRow.client, 167 server.db.nodes.find(allocation.nodeId).id.split('-')[0], 168 'Node ID' 169 ); 170 assert.equal( 171 allocationRow.clientTooltip.substr(0, 15), 172 server.db.nodes.find(allocation.nodeId).name.substr(0, 15), 173 'Node Name' 174 ); 175 assert.equal( 176 allocationRow.cpu, 177 Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 178 'CPU %' 179 ); 180 const roundedTicks = Math.floor( 181 allocStats.resourceUsage.CpuStats.TotalTicks 182 ); 183 assert.equal( 184 allocationRow.cpuTooltip, 185 `${formatHertz(roundedTicks, 'MHz')} / ${formatHertz(cpuUsed, 'MHz')}`, 186 'Detailed CPU information is in a tooltip' 187 ); 188 assert.equal( 189 allocationRow.mem, 190 allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, 191 'Memory used' 192 ); 193 assert.equal( 194 allocationRow.memTooltip, 195 `${formatBytes( 196 allocStats.resourceUsage.MemoryStats.RSS 197 )} / ${formatBytes(memoryUsed, 'MiB')}`, 198 'Detailed memory information is in a tooltip' 199 ); 200 }); 201 }); 202 203 test('each allocation should link to the allocation detail page', async function (assert) { 204 const allocation = server.create('allocation'); 205 assignWriteAlloc(volume, allocation); 206 207 await VolumeDetail.visit({ id: `${volume.id}@default` }); 208 await VolumeDetail.writeAllocations.objectAt(0).visit(); 209 210 assert.equal(currentURL(), `/allocations/${allocation.id}`); 211 }); 212 213 test('when there are no write allocations, the table presents an empty state', async function (assert) { 214 await VolumeDetail.visit({ id: `${volume.id}@default` }); 215 216 assert.ok(VolumeDetail.writeTableIsEmpty); 217 assert.equal(VolumeDetail.writeEmptyState.headline, 'No Write Allocations'); 218 }); 219 220 test('when there are no read allocations, the table presents an empty state', async function (assert) { 221 await VolumeDetail.visit({ id: `${volume.id}@default` }); 222 223 assert.ok(VolumeDetail.readTableIsEmpty); 224 assert.equal(VolumeDetail.readEmptyState.headline, 'No Read Allocations'); 225 }); 226 227 test('the constraints table shows access mode and attachment mode', async function (assert) { 228 await VolumeDetail.visit({ id: `${volume.id}@default` }); 229 230 assert.equal(VolumeDetail.constraints.accessMode, volume.accessMode); 231 assert.equal( 232 VolumeDetail.constraints.attachmentMode, 233 volume.attachmentMode 234 ); 235 }); 236 }); 237 238 // Namespace test: details shows the namespace 239 module('Acceptance | volume detail (with namespaces)', function (hooks) { 240 setupApplicationTest(hooks); 241 setupMirage(hooks); 242 243 let volume; 244 245 hooks.beforeEach(function () { 246 server.createList('namespace', 2); 247 server.create('node-pool'); 248 server.create('node'); 249 server.create('csi-plugin', { createVolumes: false }); 250 volume = server.create('csi-volume'); 251 }); 252 253 test('/csi/volumes/:id detail ribbon includes the namespace of the volume', async function (assert) { 254 await VolumeDetail.visit({ id: `${volume.id}@${volume.namespaceId}` }); 255 256 assert.ok(VolumeDetail.hasNamespace); 257 assert.ok(VolumeDetail.namespace.includes(volume.namespaceId || 'default')); 258 }); 259 });