github.com/iqoqo/nomad@v0.11.3-0.20200911112621-d7021c74d101/ui/tests/acceptance/task-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 Task from 'nomad-ui/tests/pages/allocations/task/detail'; 6 import moment from 'moment'; 7 8 let allocation; 9 let task; 10 11 module('Acceptance | task detail', function(hooks) { 12 setupApplicationTest(hooks); 13 setupMirage(hooks); 14 15 hooks.beforeEach(async function() { 16 server.create('agent'); 17 server.create('node'); 18 server.create('job', { createAllocations: false }); 19 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 20 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 21 22 await Task.visit({ id: allocation.id, name: task.name }); 23 }); 24 25 test('/allocation/:id/:task_name should name the task and list high-level task information', async function(assert) { 26 assert.ok(Task.title.text.includes(task.name), 'Task name'); 27 assert.ok(Task.state.includes(task.state), 'Task state'); 28 29 assert.ok( 30 Task.startedAt.includes(moment(task.startedAt).format("MMM DD, 'YY HH:mm:ss ZZ")), 31 'Task started at' 32 ); 33 34 const lifecycle = server.db.tasks.where({ name: task.name })[0].Lifecycle; 35 const prestartString = lifecycle && lifecycle.Sidecar ? 'sidecar' : 'prestart'; 36 assert.equal(Task.lifecycle, lifecycle ? prestartString : 'main'); 37 38 assert.equal(document.title, `Task ${task.name} - Nomad`); 39 }); 40 41 test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { 42 const { jobId, taskGroup } = allocation; 43 const job = server.db.jobs.find(jobId); 44 45 const shortId = allocation.id.split('-')[0]; 46 47 assert.equal(Task.breadcrumbFor('jobs.index').text, 'Jobs', 'Jobs is the first breadcrumb'); 48 assert.equal( 49 Task.breadcrumbFor('jobs.job.index').text, 50 job.name, 51 'Job is the second breadcrumb' 52 ); 53 assert.equal( 54 Task.breadcrumbFor('jobs.job.task-group').text, 55 taskGroup, 56 'Task Group is the third breadcrumb' 57 ); 58 assert.equal( 59 Task.breadcrumbFor('allocations.allocation').text, 60 shortId, 61 'Allocation short id is the fourth breadcrumb' 62 ); 63 assert.equal( 64 Task.breadcrumbFor('allocations.allocation.task').text, 65 task.name, 66 'Task name is the fifth breadcrumb' 67 ); 68 69 await Task.breadcrumbFor('jobs.index').visit(); 70 assert.equal(currentURL(), '/jobs', 'Jobs breadcrumb links correctly'); 71 72 await Task.visit({ id: allocation.id, name: task.name }); 73 await Task.breadcrumbFor('jobs.job.index').visit(); 74 assert.equal(currentURL(), `/jobs/${job.id}`, 'Job breadcrumb links correctly'); 75 76 await Task.visit({ id: allocation.id, name: task.name }); 77 await Task.breadcrumbFor('jobs.job.task-group').visit(); 78 assert.equal( 79 currentURL(), 80 `/jobs/${job.id}/${taskGroup}`, 81 'Task Group breadcrumb links correctly' 82 ); 83 84 await Task.visit({ id: allocation.id, name: task.name }); 85 await Task.breadcrumbFor('allocations.allocation').visit(); 86 assert.equal( 87 currentURL(), 88 `/allocations/${allocation.id}`, 89 'Allocations breadcrumb links correctly' 90 ); 91 }); 92 93 test('/allocation/:id/:task_name should include resource utilization graphs', async function(assert) { 94 assert.equal(Task.resourceCharts.length, 2, 'Two resource utilization graphs'); 95 assert.equal(Task.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); 96 assert.equal(Task.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); 97 }); 98 99 test('/allocation/:id/:task_name lists related prestart tasks for a main task when they exist', async function(assert) { 100 const job = server.create('job', { 101 groupsCount: 2, 102 groupTaskCount: 3, 103 createAllocations: false, 104 status: 'running', 105 }); 106 107 job.task_group_ids.forEach(taskGroupId => { 108 server.create('allocation', { 109 jobId: job.id, 110 taskGroup: server.db.taskGroups.find(taskGroupId).name, 111 forceRunningClientStatus: true, 112 }); 113 }); 114 115 const taskGroup = job.task_groups.models[0]; 116 const [mainTask, sidecarTask, prestartTask] = taskGroup.tasks.models; 117 118 mainTask.attrs.Lifecycle = null; 119 mainTask.save(); 120 121 sidecarTask.attrs.Lifecycle = { Sidecar: true, Hook: 'prestart' }; 122 sidecarTask.save(); 123 124 prestartTask.attrs.Lifecycle = { Sidecar: false, Hook: 'prestart' }; 125 prestartTask.save(); 126 127 taskGroup.save(); 128 129 const noPrestartTasksTaskGroup = job.task_groups.models[1]; 130 noPrestartTasksTaskGroup.tasks.models.forEach(task => { 131 task.attrs.Lifecycle = null; 132 task.save(); 133 }); 134 135 const mainTaskState = server.schema.taskStates.findBy({ name: mainTask.name }); 136 const sidecarTaskState = server.schema.taskStates.findBy({ name: sidecarTask.name }); 137 const prestartTaskState = server.schema.taskStates.findBy({ name: prestartTask.name }); 138 139 prestartTaskState.attrs.state = 'running'; 140 prestartTaskState.attrs.finishedAt = null; 141 prestartTaskState.save(); 142 143 await Task.visit({ id: mainTaskState.allocationId, name: mainTask.name }); 144 145 assert.ok(Task.hasPrestartTasks); 146 assert.equal(Task.prestartTasks.length, 2); 147 148 Task.prestartTasks[0].as(SidecarTask => { 149 assert.equal(SidecarTask.name, sidecarTask.name); 150 assert.equal(SidecarTask.state, sidecarTaskState.state); 151 assert.equal(SidecarTask.lifecycle, 'sidecar'); 152 assert.notOk(SidecarTask.isBlocking); 153 }); 154 155 Task.prestartTasks[1].as(PrestartTask => { 156 assert.equal(PrestartTask.name, prestartTask.name); 157 assert.equal(PrestartTask.state, prestartTaskState.state); 158 assert.equal(PrestartTask.lifecycle, 'prestart'); 159 assert.ok(PrestartTask.isBlocking); 160 }); 161 162 await Task.visit({ id: sidecarTaskState.allocationId, name: sidecarTask.name }); 163 164 assert.notOk(Task.hasPrestartTasks); 165 166 const noPrestartTasksTask = noPrestartTasksTaskGroup.tasks.models[0]; 167 const noPrestartTasksTaskState = server.db.taskStates.findBy({ 168 name: noPrestartTasksTask.name, 169 }); 170 171 await Task.visit({ 172 id: noPrestartTasksTaskState.allocationId, 173 name: noPrestartTasksTaskState.name, 174 }); 175 176 assert.notOk(Task.hasPrestartTasks); 177 }); 178 179 test('the addresses table lists all reserved and dynamic ports', async function(assert) { 180 const taskResources = allocation.taskResourceIds 181 .map(id => server.db.taskResources.find(id)) 182 .find(resources => resources.name === task.name); 183 const reservedPorts = taskResources.resources.Networks[0].ReservedPorts; 184 const dynamicPorts = taskResources.resources.Networks[0].DynamicPorts; 185 const addresses = reservedPorts.concat(dynamicPorts); 186 187 assert.equal(Task.addresses.length, addresses.length, 'All addresses are listed'); 188 }); 189 190 test('each address row shows the label and value of the address', async function(assert) { 191 const taskResources = allocation.taskResourceIds 192 .map(id => server.db.taskResources.find(id)) 193 .findBy('name', task.name); 194 const networkAddress = taskResources.resources.Networks[0].IP; 195 const reservedPorts = taskResources.resources.Networks[0].ReservedPorts; 196 const dynamicPorts = taskResources.resources.Networks[0].DynamicPorts; 197 const address = reservedPorts.concat(dynamicPorts).sortBy('Label')[0]; 198 199 const addressRow = Task.addresses.objectAt(0); 200 assert.equal( 201 addressRow.isDynamic, 202 reservedPorts.includes(address) ? 'No' : 'Yes', 203 'Dynamic port is denoted as such' 204 ); 205 assert.equal(addressRow.name, address.Label, 'Label'); 206 assert.equal(addressRow.address, `${networkAddress}:${address.Value}`, 'Value'); 207 }); 208 209 test('the events table lists all recent events', async function(assert) { 210 const events = server.db.taskEvents.where({ taskStateId: task.id }); 211 212 assert.equal(Task.events.length, events.length, `Lists ${events.length} events`); 213 }); 214 215 test('when a task has volumes, the volumes table is shown', async function(assert) { 216 const taskGroup = server.schema.taskGroups.where({ 217 jobId: allocation.jobId, 218 name: allocation.taskGroup, 219 }).models[0]; 220 221 const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); 222 223 assert.ok(Task.hasVolumes); 224 assert.equal(Task.volumes.length, jobTask.volumeMounts.length); 225 }); 226 227 test('when a task does not have volumes, the volumes table is not shown', async function(assert) { 228 const job = server.create('job', { createAllocations: false, noHostVolumes: true }); 229 allocation = server.create('allocation', { jobId: job.id, clientStatus: 'running' }); 230 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 231 232 await Task.visit({ id: allocation.id, name: task.name }); 233 assert.notOk(Task.hasVolumes); 234 }); 235 236 test('each volume in the volumes table shows information about the volume', async function(assert) { 237 const taskGroup = server.schema.taskGroups.where({ 238 jobId: allocation.jobId, 239 name: allocation.taskGroup, 240 }).models[0]; 241 242 const jobTask = taskGroup.tasks.models.find(m => m.name === task.name); 243 const volume = jobTask.volumeMounts[0]; 244 245 Task.volumes[0].as(volumeRow => { 246 assert.equal(volumeRow.name, volume.Volume); 247 assert.equal(volumeRow.destination, volume.Destination); 248 assert.equal(volumeRow.permissions, volume.ReadOnly ? 'Read' : 'Read/Write'); 249 assert.equal(volumeRow.clientSource, taskGroup.volumes[volume.Volume].Source); 250 }); 251 }); 252 253 test('each recent event should list the time, type, and description of the event', async function(assert) { 254 const event = server.db.taskEvents.where({ taskStateId: task.id })[0]; 255 const recentEvent = Task.events.objectAt(Task.events.length - 1); 256 257 assert.equal( 258 recentEvent.time, 259 moment(event.time / 1000000).format("MMM DD, 'YY HH:mm:ss ZZ"), 260 'Event timestamp' 261 ); 262 assert.equal(recentEvent.type, event.type, 'Event type'); 263 assert.equal(recentEvent.message, event.displayMessage, 'Event message'); 264 }); 265 266 test('when the allocation is not found, the application errors', async function(assert) { 267 await Task.visit({ id: 'not-a-real-allocation', name: task.name }); 268 269 assert.equal( 270 server.pretender.handledRequests 271 .filter(request => !request.url.includes('policy')) 272 .findBy('status', 404).url, 273 '/v1/allocation/not-a-real-allocation', 274 'A request to the nonexistent allocation is made' 275 ); 276 assert.equal( 277 currentURL(), 278 `/allocations/not-a-real-allocation/${task.name}`, 279 'The URL persists' 280 ); 281 assert.ok(Task.error.isPresent, 'Error message is shown'); 282 assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); 283 }); 284 285 test('when the allocation is found but the task is not, the application errors', async function(assert) { 286 await Task.visit({ id: allocation.id, name: 'not-a-real-task-name' }); 287 288 assert.ok( 289 server.pretender.handledRequests 290 .filterBy('status', 200) 291 .mapBy('url') 292 .includes(`/v1/allocation/${allocation.id}`), 293 'A request to the allocation is made successfully' 294 ); 295 assert.equal( 296 currentURL(), 297 `/allocations/${allocation.id}/not-a-real-task-name`, 298 'The URL persists' 299 ); 300 assert.ok(Task.error.isPresent, 'Error message is shown'); 301 assert.equal(Task.error.title, 'Not Found', 'Error message is for 404'); 302 }); 303 304 test('task can be restarted', async function(assert) { 305 await Task.restart.idle(); 306 await Task.restart.confirm(); 307 308 const request = server.pretender.handledRequests.findBy('method', 'PUT'); 309 assert.equal( 310 request.url, 311 `/v1/client/allocation/${allocation.id}/restart`, 312 'Restart request is made for the allocation' 313 ); 314 315 assert.deepEqual( 316 JSON.parse(request.requestBody), 317 { TaskName: task.name }, 318 'Restart request is made for the correct task' 319 ); 320 }); 321 322 test('when task restart fails, an error message is shown', async function(assert) { 323 server.pretender.put('/v1/client/allocation/:id/restart', () => [403, {}, '']); 324 325 await Task.restart.idle(); 326 await Task.restart.confirm(); 327 328 assert.ok(Task.inlineError.isShown, 'Inline error is shown'); 329 assert.ok(Task.inlineError.title.includes('Could Not Restart Task'), 'Title is descriptive'); 330 assert.ok( 331 /ACL token.+?allocation lifecycle/.test(Task.inlineError.message), 332 'Message mentions ACLs and the appropriate permission' 333 ); 334 335 await Task.inlineError.dismiss(); 336 337 assert.notOk(Task.inlineError.isShown, 'Inline error is no longer shown'); 338 }); 339 340 test('exec button is present', async function(assert) { 341 assert.ok(Task.execButton.isPresent); 342 }); 343 }); 344 345 module('Acceptance | task detail (no addresses)', function(hooks) { 346 setupApplicationTest(hooks); 347 setupMirage(hooks); 348 349 hooks.beforeEach(async function() { 350 server.create('agent'); 351 server.create('node'); 352 server.create('job'); 353 allocation = server.create('allocation', 'withoutTaskWithPorts', { clientStatus: 'running' }); 354 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 355 356 await Task.visit({ id: allocation.id, name: task.name }); 357 }); 358 359 test('when the task has no addresses, the addresses table is not shown', async function(assert) { 360 assert.notOk(Task.hasAddresses, 'No addresses table'); 361 }); 362 }); 363 364 module('Acceptance | task detail (different namespace)', function(hooks) { 365 setupApplicationTest(hooks); 366 setupMirage(hooks); 367 368 hooks.beforeEach(async function() { 369 server.create('agent'); 370 server.create('node'); 371 server.create('namespace'); 372 server.create('namespace', { id: 'other-namespace' }); 373 server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); 374 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 375 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 376 377 await Task.visit({ id: allocation.id, name: task.name }); 378 }); 379 380 test('breadcrumbs match jobs / job / task group / allocation / task', async function(assert) { 381 const { jobId, taskGroup } = allocation; 382 const job = server.db.jobs.find(jobId); 383 384 await Task.breadcrumbFor('jobs.index').visit(); 385 assert.equal( 386 currentURL(), 387 '/jobs?namespace=other-namespace', 388 'Jobs breadcrumb links correctly' 389 ); 390 391 await Task.visit({ id: allocation.id, name: task.name }); 392 await Task.breadcrumbFor('jobs.job.index').visit(); 393 assert.equal( 394 currentURL(), 395 `/jobs/${job.id}?namespace=other-namespace`, 396 'Job breadcrumb links correctly' 397 ); 398 399 await Task.visit({ id: allocation.id, name: task.name }); 400 await Task.breadcrumbFor('jobs.job.task-group').visit(); 401 assert.equal( 402 currentURL(), 403 `/jobs/${job.id}/${taskGroup}?namespace=other-namespace`, 404 'Task Group breadcrumb links correctly' 405 ); 406 407 await Task.visit({ id: allocation.id, name: task.name }); 408 await Task.breadcrumbFor('allocations.allocation').visit(); 409 assert.equal( 410 currentURL(), 411 `/allocations/${allocation.id}`, 412 'Allocations breadcrumb links correctly' 413 ); 414 }); 415 }); 416 417 module('Acceptance | task detail (not running)', function(hooks) { 418 setupApplicationTest(hooks); 419 setupMirage(hooks); 420 421 hooks.beforeEach(async function() { 422 server.create('agent'); 423 server.create('node'); 424 server.create('namespace'); 425 server.create('namespace', { id: 'other-namespace' }); 426 server.create('job', { createAllocations: false, namespaceId: 'other-namespace' }); 427 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'complete' }); 428 task = server.db.taskStates.where({ allocationId: allocation.id })[0]; 429 430 await Task.visit({ id: allocation.id, name: task.name }); 431 }); 432 433 test('when the allocation for a task is not running, the resource utilization graphs are replaced by an empty message', async function(assert) { 434 assert.equal(Task.resourceCharts.length, 0, 'No resource charts'); 435 assert.equal(Task.resourceEmptyMessage, "Task isn't running", 'Empty message is appropriate'); 436 }); 437 438 test('exec button is absent', async function(assert) { 439 assert.notOk(Task.execButton.isPresent); 440 }); 441 }); 442 443 module('Acceptance | proxy task detail', function(hooks) { 444 setupApplicationTest(hooks); 445 setupMirage(hooks); 446 447 hooks.beforeEach(async function() { 448 server.create('agent'); 449 server.create('node'); 450 server.create('job', { createAllocations: false }); 451 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 452 453 const taskState = allocation.task_states.models[0]; 454 const task = server.schema.tasks.findBy({ name: taskState.name }); 455 task.update('kind', 'connect-proxy:task'); 456 task.save(); 457 458 await Task.visit({ id: allocation.id, name: taskState.name }); 459 }); 460 461 test('a proxy tag is shown', async function(assert) { 462 assert.ok(Task.title.proxyTag.isPresent); 463 }); 464 });