github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/ui/tests/acceptance/task-group-detail-test.js (about) 1 import { currentURL } from '@ember/test-helpers'; 2 import { module, test } from 'qunit'; 3 import { setupApplicationTest } from 'ember-qunit'; 4 import { setupMirage } from 'ember-cli-mirage/test-support'; 5 import { formatBytes } from 'nomad-ui/helpers/format-bytes'; 6 import TaskGroup from 'nomad-ui/tests/pages/jobs/job/task-group'; 7 import pageSizeSelect from './behaviors/page-size-select'; 8 import moment from 'moment'; 9 10 let job; 11 let taskGroup; 12 let tasks; 13 let allocations; 14 15 const sum = (total, n) => total + n; 16 17 module('Acceptance | task group detail', function(hooks) { 18 setupApplicationTest(hooks); 19 setupMirage(hooks); 20 21 hooks.beforeEach(async function() { 22 server.create('agent'); 23 server.create('node', 'forceIPv4'); 24 25 job = server.create('job', { 26 groupsCount: 2, 27 createAllocations: false, 28 }); 29 30 const taskGroups = server.db.taskGroups.where({ jobId: job.id }); 31 taskGroup = taskGroups[0]; 32 33 tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); 34 35 server.create('node', 'forceIPv4'); 36 37 allocations = server.createList('allocation', 2, { 38 jobId: job.id, 39 taskGroup: taskGroup.name, 40 clientStatus: 'running', 41 }); 42 43 // Allocations associated to a different task group on the job to 44 // assert that they aren't showing up in on this page in error. 45 server.createList('allocation', 3, { 46 jobId: job.id, 47 taskGroup: taskGroups[1].name, 48 clientStatus: 'running', 49 }); 50 51 // Set a static name to make the search test deterministic 52 server.db.allocations.forEach(alloc => { 53 alloc.name = 'aaaaa'; 54 }); 55 56 // Mark the first alloc as rescheduled 57 allocations[0].update({ 58 nextAllocation: allocations[1].id, 59 }); 60 allocations[1].update({ 61 previousAllocation: allocations[0].id, 62 }); 63 64 window.localStorage.clear(); 65 }); 66 67 test('/jobs/:id/:task-group should list high-level metrics for the allocation', async function(assert) { 68 const totalCPU = tasks.mapBy('Resources.CPU').reduce(sum, 0); 69 const totalMemory = tasks.mapBy('Resources.MemoryMB').reduce(sum, 0); 70 const totalDisk = taskGroup.ephemeralDisk.SizeMB; 71 72 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 73 74 assert.equal(TaskGroup.tasksCount, `# Tasks ${tasks.length}`, '# Tasks'); 75 assert.equal( 76 TaskGroup.cpu, 77 `Reserved CPU ${totalCPU} MHz`, 78 'Aggregated CPU reservation for all tasks' 79 ); 80 assert.equal( 81 TaskGroup.mem, 82 `Reserved Memory ${totalMemory} MiB`, 83 'Aggregated Memory reservation for all tasks' 84 ); 85 assert.equal( 86 TaskGroup.disk, 87 `Reserved Disk ${totalDisk} MiB`, 88 'Aggregated Disk reservation for all tasks' 89 ); 90 91 assert.equal(document.title, `Task group ${taskGroup.name} - Job ${job.name} - Nomad`); 92 }); 93 94 test('/jobs/:id/:task-group should have breadcrumbs for job and jobs', async function(assert) { 95 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 96 97 assert.equal(TaskGroup.breadcrumbFor('jobs.index').text, 'Jobs', 'First breadcrumb says jobs'); 98 assert.equal( 99 TaskGroup.breadcrumbFor('jobs.job.index').text, 100 job.name, 101 'Second breadcrumb says the job name' 102 ); 103 assert.equal( 104 TaskGroup.breadcrumbFor('jobs.job.task-group').text, 105 taskGroup.name, 106 'Third breadcrumb says the job name' 107 ); 108 }); 109 110 test('/jobs/:id/:task-group first breadcrumb should link to jobs', async function(assert) { 111 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 112 113 await TaskGroup.breadcrumbFor('jobs.index').visit(); 114 assert.equal(currentURL(), '/jobs', 'First breadcrumb links back to jobs'); 115 }); 116 117 test('/jobs/:id/:task-group second breadcrumb should link to the job for the task group', async function(assert) { 118 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 119 120 await TaskGroup.breadcrumbFor('jobs.job.index').visit(); 121 assert.equal( 122 currentURL(), 123 `/jobs/${job.id}`, 124 'Second breadcrumb links back to the job for the task group' 125 ); 126 }); 127 128 test('/jobs/:id/:task-group should list one page of allocations for the task group', async function(assert) { 129 server.createList('allocation', TaskGroup.pageSize, { 130 jobId: job.id, 131 taskGroup: taskGroup.name, 132 clientStatus: 'running', 133 }); 134 135 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 136 137 assert.ok( 138 server.db.allocations.where({ jobId: job.id }).length > TaskGroup.pageSize, 139 'There are enough allocations to invoke pagination' 140 ); 141 142 assert.equal( 143 TaskGroup.allocations.length, 144 TaskGroup.pageSize, 145 'All allocations for the task group' 146 ); 147 }); 148 149 test('each allocation should show basic information about the allocation', async function(assert) { 150 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 151 152 const allocation = allocations.sortBy('modifyIndex').reverse()[0]; 153 const allocationRow = TaskGroup.allocations.objectAt(0); 154 155 assert.equal(allocationRow.shortId, allocation.id.split('-')[0], 'Allocation short id'); 156 assert.equal( 157 allocationRow.createTime, 158 moment(allocation.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), 159 'Allocation create time' 160 ); 161 assert.equal( 162 allocationRow.modifyTime, 163 moment(allocation.modifyTime / 1000000).fromNow(), 164 'Allocation modify time' 165 ); 166 assert.equal(allocationRow.status, allocation.clientStatus, 'Client status'); 167 assert.equal(allocationRow.jobVersion, allocation.jobVersion, 'Job Version'); 168 assert.equal( 169 allocationRow.client, 170 server.db.nodes.find(allocation.nodeId).id.split('-')[0], 171 'Node ID' 172 ); 173 assert.equal( 174 allocationRow.volume, 175 Object.keys(taskGroup.volumes).length ? 'Yes' : '', 176 'Volumes' 177 ); 178 179 await allocationRow.visitClient(); 180 181 assert.equal(currentURL(), `/clients/${allocation.nodeId}`, 'Node links to node page'); 182 }); 183 184 test('each allocation should show stats about the allocation', async function(assert) { 185 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 186 187 const allocation = allocations.sortBy('name')[0]; 188 const allocationRow = TaskGroup.allocations.objectAt(0); 189 190 const allocStats = server.db.clientAllocationStats.find(allocation.id); 191 const tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); 192 193 const cpuUsed = tasks.reduce((sum, task) => sum + task.Resources.CPU, 0); 194 const memoryUsed = tasks.reduce((sum, task) => sum + task.Resources.MemoryMB, 0); 195 196 assert.equal( 197 allocationRow.cpu, 198 Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks) / cpuUsed, 199 'CPU %' 200 ); 201 202 assert.equal( 203 allocationRow.cpuTooltip, 204 `${Math.floor(allocStats.resourceUsage.CpuStats.TotalTicks)} / ${cpuUsed} MHz`, 205 'Detailed CPU information is in a tooltip' 206 ); 207 208 assert.equal( 209 allocationRow.mem, 210 allocStats.resourceUsage.MemoryStats.RSS / 1024 / 1024 / memoryUsed, 211 'Memory used' 212 ); 213 214 assert.equal( 215 allocationRow.memTooltip, 216 `${formatBytes([allocStats.resourceUsage.MemoryStats.RSS])} / ${memoryUsed} MiB`, 217 'Detailed memory information is in a tooltip' 218 ); 219 }); 220 221 test('when the allocation search has no matches, there is an empty message', async function(assert) { 222 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 223 224 await TaskGroup.search('zzzzzz'); 225 226 assert.ok(TaskGroup.isEmpty, 'Empty state is shown'); 227 assert.equal( 228 TaskGroup.emptyState.headline, 229 'No Matches', 230 'Empty state has an appropriate message' 231 ); 232 }); 233 234 test('when the allocation has reschedule events, the allocation row is denoted with an icon', async function(assert) { 235 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 236 237 const rescheduleRow = TaskGroup.allocationFor(allocations[0].id); 238 const normalRow = TaskGroup.allocationFor(allocations[1].id); 239 240 assert.ok(rescheduleRow.rescheduled, 'Reschedule row has a reschedule icon'); 241 assert.notOk(normalRow.rescheduled, 'Normal row has no reschedule icon'); 242 }); 243 244 test('/jobs/:id/:task-group should present task lifecycles', async function(assert) { 245 job = server.create('job', { 246 groupsCount: 2, 247 groupTaskCount: 3, 248 }); 249 250 const taskGroups = server.db.taskGroups.where({ jobId: job.id }); 251 taskGroup = taskGroups[0]; 252 253 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 254 255 assert.ok(TaskGroup.lifecycleChart.isPresent); 256 assert.equal(TaskGroup.lifecycleChart.title, 'Task Lifecycle Configuration'); 257 258 tasks = taskGroup.taskIds.map(id => server.db.tasks.find(id)); 259 const taskNames = tasks.mapBy('name'); 260 261 // This is thoroughly tested in allocation detail tests, so this mostly checks what’s different 262 263 assert.equal(TaskGroup.lifecycleChart.tasks.length, 3); 264 265 TaskGroup.lifecycleChart.tasks.forEach(Task => { 266 assert.ok(taskNames.includes(Task.name)); 267 assert.notOk(Task.isActive); 268 assert.notOk(Task.isFinished); 269 }); 270 }); 271 272 test('when the task group depends on volumes, the volumes table is shown', async function(assert) { 273 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 274 275 assert.ok(TaskGroup.hasVolumes); 276 assert.equal(TaskGroup.volumes.length, Object.keys(taskGroup.volumes).length); 277 }); 278 279 test('when the task group does not depend on volumes, the volumes table is not shown', async function(assert) { 280 job = server.create('job', { noHostVolumes: true, shallow: true }); 281 taskGroup = server.db.taskGroups.where({ jobId: job.id })[0]; 282 283 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 284 285 assert.notOk(TaskGroup.hasVolumes); 286 }); 287 288 test('each row in the volumes table lists information about the volume', async function(assert) { 289 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 290 291 TaskGroup.volumes[0].as(volumeRow => { 292 const volume = taskGroup.volumes[volumeRow.name]; 293 assert.equal(volumeRow.name, volume.Name); 294 assert.equal(volumeRow.type, volume.Type); 295 assert.equal(volumeRow.source, volume.Source); 296 assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write'); 297 }); 298 }); 299 300 test('when the job for the task group is not found, an error message is shown, but the URL persists', async function(assert) { 301 await TaskGroup.visit({ id: 'not-a-real-job', name: 'not-a-real-task-group' }); 302 303 assert.equal( 304 server.pretender.handledRequests 305 .filter(request => !request.url.includes('policy')) 306 .findBy('status', 404).url, 307 '/v1/job/not-a-real-job', 308 'A request to the nonexistent job is made' 309 ); 310 assert.equal(currentURL(), '/jobs/not-a-real-job/not-a-real-task-group', 'The URL persists'); 311 assert.ok(TaskGroup.error.isPresent, 'Error message is shown'); 312 assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404'); 313 }); 314 315 test('when the task group is not found on the job, an error message is shown, but the URL persists', async function(assert) { 316 await TaskGroup.visit({ id: job.id, name: 'not-a-real-task-group' }); 317 318 assert.ok( 319 server.pretender.handledRequests 320 .filterBy('status', 200) 321 .mapBy('url') 322 .includes(`/v1/job/${job.id}`), 323 'A request to the job is made and succeeds' 324 ); 325 assert.equal(currentURL(), `/jobs/${job.id}/not-a-real-task-group`, 'The URL persists'); 326 assert.ok(TaskGroup.error.isPresent, 'Error message is shown'); 327 assert.equal(TaskGroup.error.title, 'Not Found', 'Error message is for 404'); 328 }); 329 330 pageSizeSelect({ 331 resourceName: 'allocation', 332 pageObject: TaskGroup, 333 pageObjectList: TaskGroup.allocations, 334 async setup() { 335 server.createList('allocation', TaskGroup.pageSize, { 336 jobId: job.id, 337 taskGroup: taskGroup.name, 338 clientStatus: 'running', 339 }); 340 341 await TaskGroup.visit({ id: job.id, name: taskGroup.name }); 342 }, 343 }); 344 });