github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/ui/tests/acceptance/allocation-detail-test.js (about) 1 import { run } from '@ember/runloop'; 2 import { currentURL } from '@ember/test-helpers'; 3 import { assign } from '@ember/polyfills'; 4 import { module, test } from 'qunit'; 5 import { setupApplicationTest } from 'ember-qunit'; 6 import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; 7 import Allocation from 'nomad-ui/tests/pages/allocations/detail'; 8 import moment from 'moment'; 9 10 let job; 11 let node; 12 let allocation; 13 14 module('Acceptance | allocation detail', function(hooks) { 15 setupApplicationTest(hooks); 16 setupMirage(hooks); 17 18 hooks.beforeEach(async function() { 19 server.create('agent'); 20 21 node = server.create('node'); 22 job = server.create('job', { groupsCount: 1, createAllocations: false }); 23 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'running' }); 24 25 // Make sure the node has an unhealthy driver 26 node.update({ 27 driver: assign(node.drivers, { 28 docker: { 29 detected: true, 30 healthy: false, 31 }, 32 }), 33 }); 34 35 // Make sure a task for the allocation depends on the unhealthy driver 36 server.schema.tasks.first().update({ 37 driver: 'docker', 38 }); 39 40 await Allocation.visit({ id: allocation.id }); 41 }); 42 43 test('/allocation/:id should name the allocation and link to the corresponding job and node', async function(assert) { 44 assert.ok(Allocation.title.includes(allocation.name), 'Allocation name is in the heading'); 45 assert.equal(Allocation.details.job, job.name, 'Job name is in the subheading'); 46 assert.equal( 47 Allocation.details.client, 48 node.id.split('-')[0], 49 'Node short id is in the subheading' 50 ); 51 52 await Allocation.details.visitJob(); 53 assert.equal(currentURL(), `/jobs/${job.id}`, 'Job link navigates to the job'); 54 55 await Allocation.visit({ id: allocation.id }); 56 57 await Allocation.details.visitClient(); 58 assert.equal(currentURL(), `/clients/${node.id}`, 'Client link navigates to the client'); 59 }); 60 61 test('/allocation/:id should include resource utilization graphs', async function(assert) { 62 assert.equal(Allocation.resourceCharts.length, 2, 'Two resource utilization graphs'); 63 assert.equal(Allocation.resourceCharts.objectAt(0).name, 'CPU', 'First chart is CPU'); 64 assert.equal(Allocation.resourceCharts.objectAt(1).name, 'Memory', 'Second chart is Memory'); 65 }); 66 67 test('/allocation/:id should list all tasks for the allocation', async function(assert) { 68 assert.equal( 69 Allocation.tasks.length, 70 server.db.taskStates.where({ allocationId: allocation.id }).length, 71 'Table lists all tasks' 72 ); 73 assert.notOk(Allocation.isEmpty, 'Task table empty state is not shown'); 74 }); 75 76 test('each task row should list high-level information for the task', async function(assert) { 77 const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0]; 78 const taskResources = allocation.taskResourceIds 79 .map(id => server.db.taskResources.find(id)) 80 .sortBy('name')[0]; 81 const reservedPorts = taskResources.resources.Networks[0].ReservedPorts; 82 const dynamicPorts = taskResources.resources.Networks[0].DynamicPorts; 83 const taskRow = Allocation.tasks.objectAt(0); 84 const events = server.db.taskEvents.where({ taskStateId: task.id }); 85 const event = events[events.length - 1]; 86 87 assert.equal(taskRow.name, task.name, 'Name'); 88 assert.equal(taskRow.state, task.state, 'State'); 89 assert.equal(taskRow.message, event.displayMessage, 'Event Message'); 90 assert.equal( 91 taskRow.time, 92 moment(event.time / 1000000).format("MMM DD, 'YY HH:mm:ss ZZ"), 93 'Event Time' 94 ); 95 96 assert.ok(reservedPorts.length, 'The task has reserved ports'); 97 assert.ok(dynamicPorts.length, 'The task has dynamic ports'); 98 99 const addressesText = taskRow.ports; 100 reservedPorts.forEach(port => { 101 assert.ok(addressesText.includes(port.Label), `Found label ${port.Label}`); 102 assert.ok(addressesText.includes(port.Value), `Found value ${port.Value}`); 103 }); 104 dynamicPorts.forEach(port => { 105 assert.ok(addressesText.includes(port.Label), `Found label ${port.Label}`); 106 assert.ok(addressesText.includes(port.Value), `Found value ${port.Value}`); 107 }); 108 }); 109 110 test('each task row should link to the task detail page', async function(assert) { 111 const task = server.db.taskStates.where({ allocationId: allocation.id }).sortBy('name')[0]; 112 113 await Allocation.tasks.objectAt(0).clickLink(); 114 115 assert.equal( 116 currentURL(), 117 `/allocations/${allocation.id}/${task.name}`, 118 'Task name in task row links to task detail' 119 ); 120 121 await Allocation.visit({ id: allocation.id }); 122 await Allocation.tasks.objectAt(0).clickRow(); 123 124 assert.equal( 125 currentURL(), 126 `/allocations/${allocation.id}/${task.name}`, 127 'Task row links to task detail' 128 ); 129 }); 130 131 test('tasks with an unhealthy driver have a warning icon', async function(assert) { 132 assert.ok(Allocation.firstUnhealthyTask().hasUnhealthyDriver, 'Warning is shown'); 133 }); 134 135 test('when there are no tasks, an empty state is shown', async function(assert) { 136 // Make sure the allocation is pending in order to ensure there are no tasks 137 allocation = server.create('allocation', 'withTaskWithPorts', { clientStatus: 'pending' }); 138 await Allocation.visit({ id: allocation.id }); 139 140 assert.ok(Allocation.isEmpty, 'Task table empty state is shown'); 141 }); 142 143 test('when the allocation has not been rescheduled, the reschedule events section is not rendered', async function(assert) { 144 assert.notOk(Allocation.hasRescheduleEvents, 'Reschedule Events section exists'); 145 }); 146 147 test('when the allocation is not found, an error message is shown, but the URL persists', async function(assert) { 148 await Allocation.visit({ id: 'not-a-real-allocation' }); 149 150 assert.equal( 151 server.pretender.handledRequests.findBy('status', 404).url, 152 '/v1/allocation/not-a-real-allocation', 153 'A request to the nonexistent allocation is made' 154 ); 155 assert.equal(currentURL(), '/allocations/not-a-real-allocation', 'The URL persists'); 156 assert.ok(Allocation.error.isShown, 'Error message is shown'); 157 assert.equal(Allocation.error.title, 'Not Found', 'Error message is for 404'); 158 }); 159 160 test('allocation can be stopped', async function(assert) { 161 await Allocation.stop.idle(); 162 await Allocation.stop.confirm(); 163 164 assert.equal( 165 server.pretender.handledRequests.findBy('method', 'POST').url, 166 `/v1/allocation/${allocation.id}/stop`, 167 'Stop request is made for the allocation' 168 ); 169 }); 170 171 test('allocation can be restarted', async function(assert) { 172 await Allocation.restart.idle(); 173 await Allocation.restart.confirm(); 174 175 assert.equal( 176 server.pretender.handledRequests.findBy('method', 'PUT').url, 177 `/v1/client/allocation/${allocation.id}/restart`, 178 'Restart request is made for the allocation' 179 ); 180 }); 181 182 test('while an allocation is being restarted, the stop button is disabled', async function(assert) { 183 server.pretender.post('/v1/allocation/:id/stop', () => [204, {}, ''], true); 184 185 await Allocation.stop.idle(); 186 187 run.later(() => { 188 assert.ok(Allocation.stop.isRunning, 'Stop is loading'); 189 assert.ok(Allocation.restart.isDisabled, 'Restart is disabled'); 190 server.pretender.resolve(server.pretender.requestReferences[0].request); 191 }, 500); 192 193 await Allocation.stop.confirm(); 194 }); 195 196 test('if stopping or restarting fails, an error message is shown', async function(assert) { 197 server.pretender.post('/v1/allocation/:id/stop', () => [403, {}, '']); 198 199 await Allocation.stop.idle(); 200 await Allocation.stop.confirm(); 201 202 assert.ok(Allocation.inlineError.isShown, 'Inline error is shown'); 203 assert.ok( 204 Allocation.inlineError.title.includes('Could Not Stop Allocation'), 205 'Title is descriptive' 206 ); 207 assert.ok( 208 /ACL token.+?allocation lifecycle/.test(Allocation.inlineError.message), 209 'Message mentions ACLs and the appropriate permission' 210 ); 211 212 await Allocation.inlineError.dismiss(); 213 214 assert.notOk(Allocation.inlineError.isShown, 'Inline error is no longer shown'); 215 }); 216 }); 217 218 module('Acceptance | allocation detail (rescheduled)', function(hooks) { 219 setupApplicationTest(hooks); 220 setupMirage(hooks); 221 222 hooks.beforeEach(async function() { 223 server.create('agent'); 224 225 node = server.create('node'); 226 job = server.create('job', { createAllocations: false }); 227 allocation = server.create('allocation', 'rescheduled'); 228 229 await Allocation.visit({ id: allocation.id }); 230 }); 231 232 test('when the allocation has been rescheduled, the reschedule events section is rendered', async function(assert) { 233 assert.ok(Allocation.hasRescheduleEvents, 'Reschedule Events section exists'); 234 }); 235 }); 236 237 module('Acceptance | allocation detail (not running)', function(hooks) { 238 setupApplicationTest(hooks); 239 setupMirage(hooks); 240 241 hooks.beforeEach(async function() { 242 server.create('agent'); 243 244 node = server.create('node'); 245 job = server.create('job', { createAllocations: false }); 246 allocation = server.create('allocation', { clientStatus: 'pending' }); 247 248 await Allocation.visit({ id: allocation.id }); 249 }); 250 251 test('when the allocation is not running, the utilization graphs are replaced by an empty message', async function(assert) { 252 assert.equal(Allocation.resourceCharts.length, 0, 'No resource charts'); 253 assert.equal( 254 Allocation.resourceEmptyMessage, 255 "Allocation isn't running", 256 'Empty message is appropriate' 257 ); 258 }); 259 }); 260 261 module('Acceptance | allocation detail (preemptions)', function(hooks) { 262 setupApplicationTest(hooks); 263 setupMirage(hooks); 264 265 hooks.beforeEach(async function() { 266 server.create('agent'); 267 node = server.create('node'); 268 job = server.create('job', { createAllocations: false }); 269 }); 270 271 test('shows a dedicated section to the allocation that preempted this allocation', async function(assert) { 272 allocation = server.create('allocation', 'preempted'); 273 const preempter = server.schema.find('allocation', allocation.preemptedByAllocation); 274 const preempterJob = server.schema.find('job', preempter.jobId); 275 const preempterClient = server.schema.find('node', preempter.nodeId); 276 277 await Allocation.visit({ id: allocation.id }); 278 assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown'); 279 assert.equal(Allocation.preempter.status, preempter.clientStatus, 'Preempter status matches'); 280 assert.equal(Allocation.preempter.name, preempter.name, 'Preempter name matches'); 281 assert.equal( 282 Allocation.preempter.priority, 283 preempterJob.priority, 284 'Preempter priority matches' 285 ); 286 287 await Allocation.preempter.visit(); 288 assert.equal( 289 currentURL(), 290 `/allocations/${preempter.id}`, 291 'Clicking the preempter id navigates to the preempter allocation detail page' 292 ); 293 294 await Allocation.visit({ id: allocation.id }); 295 await Allocation.preempter.visitJob(); 296 assert.equal( 297 currentURL(), 298 `/jobs/${preempterJob.id}`, 299 'Clicking the preempter job link navigates to the preempter job page' 300 ); 301 302 await Allocation.visit({ id: allocation.id }); 303 await Allocation.preempter.visitClient(); 304 assert.equal( 305 currentURL(), 306 `/clients/${preempterClient.id}`, 307 'Clicking the preempter client link navigates to the preempter client page' 308 ); 309 }); 310 311 test('shows a dedicated section to the allocations this allocation preempted', async function(assert) { 312 allocation = server.create('allocation', 'preempter'); 313 await Allocation.visit({ id: allocation.id }); 314 assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown'); 315 }); 316 317 test('each preempted allocation in the table lists basic allocation information', async function(assert) { 318 allocation = server.create('allocation', 'preempter'); 319 await Allocation.visit({ id: allocation.id }); 320 321 const preemption = allocation.preemptedAllocations 322 .map(id => server.schema.find('allocation', id)) 323 .sortBy('modifyIndex') 324 .reverse()[0]; 325 const preemptionRow = Allocation.preemptions.objectAt(0); 326 327 assert.equal( 328 Allocation.preemptions.length, 329 allocation.preemptedAllocations.length, 330 'The preemptions table has a row for each preempted allocation' 331 ); 332 333 assert.equal(preemptionRow.shortId, preemption.id.split('-')[0], 'Preemption short id'); 334 assert.equal( 335 preemptionRow.createTime, 336 moment(preemption.createTime / 1000000).format('MMM DD HH:mm:ss ZZ'), 337 'Preemption create time' 338 ); 339 assert.equal( 340 preemptionRow.modifyTime, 341 moment(preemption.modifyTime / 1000000).fromNow(), 342 'Preemption modify time' 343 ); 344 assert.equal(preemptionRow.status, preemption.clientStatus, 'Client status'); 345 assert.equal(preemptionRow.jobVersion, preemption.jobVersion, 'Job Version'); 346 assert.equal( 347 preemptionRow.client, 348 server.db.nodes.find(preemption.nodeId).id.split('-')[0], 349 'Node ID' 350 ); 351 352 await preemptionRow.visitClient(); 353 assert.equal(currentURL(), `/clients/${preemption.nodeId}`, 'Node links to node page'); 354 }); 355 356 test('when an allocation both preempted allocations and was preempted itself, both preemptions sections are shown', async function(assert) { 357 allocation = server.create('allocation', 'preempter', 'preempted'); 358 await Allocation.visit({ id: allocation.id }); 359 assert.ok(Allocation.preempted, 'The allocations this allocation preempted are shown'); 360 assert.ok(Allocation.wasPreempted, 'Preempted allocation section is shown'); 361 }); 362 });