github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/ui/tests/acceptance/topology-test.js (about) 1 /* eslint-disable qunit/require-expect */ 2 import { get } from '@ember/object'; 3 import { currentURL, typeIn, click } from '@ember/test-helpers'; 4 import { module, test } from 'qunit'; 5 import { setupApplicationTest } from 'ember-qunit'; 6 import { setupMirage } from 'ember-cli-mirage/test-support'; 7 import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit'; 8 import Topology from 'nomad-ui/tests/pages/topology'; 9 import { 10 formatBytes, 11 formatScheduledBytes, 12 formatHertz, 13 formatScheduledHertz, 14 } from 'nomad-ui/utils/units'; 15 import queryString from 'query-string'; 16 import percySnapshot from '@percy/ember'; 17 import faker from 'nomad-ui/mirage/faker'; 18 19 const sumResources = (list, dimension) => 20 list.reduce((agg, val) => agg + (get(val, dimension) || 0), 0); 21 22 module('Acceptance | topology', function (hooks) { 23 setupApplicationTest(hooks); 24 setupMirage(hooks); 25 26 hooks.beforeEach(function () { 27 server.create('job', { createAllocations: false }); 28 }); 29 30 test('it passes an accessibility audit', async function (assert) { 31 assert.expect(1); 32 33 server.createList('node', 3); 34 server.createList('allocation', 5); 35 36 await Topology.visit(); 37 await a11yAudit(assert); 38 }); 39 40 test('by default the info panel shows cluster aggregate stats', async function (assert) { 41 faker.seed(1); 42 server.createList('node', 3); 43 server.createList('allocation', 5); 44 45 await Topology.visit(); 46 47 await percySnapshot(assert); 48 49 assert.equal(Topology.infoPanelTitle, 'Cluster Details'); 50 assert.notOk(Topology.filteredNodesWarning.isPresent); 51 52 assert.equal( 53 Topology.clusterInfoPanel.nodeCount, 54 `${server.schema.nodes.all().length} Clients` 55 ); 56 57 const allocs = server.schema.allocations.all().models; 58 const scheduledAllocs = allocs.filter((alloc) => 59 ['pending', 'running'].includes(alloc.clientStatus) 60 ); 61 assert.equal( 62 Topology.clusterInfoPanel.allocCount, 63 `${scheduledAllocs.length} Allocations` 64 ); 65 66 const nodeResources = server.schema.nodes 67 .all() 68 .models.mapBy('nodeResources'); 69 const taskResources = scheduledAllocs 70 .mapBy('taskResources.models') 71 .flat() 72 .mapBy('resources'); 73 74 const totalMem = sumResources(nodeResources, 'Memory.MemoryMB'); 75 const totalCPU = sumResources(nodeResources, 'Cpu.CpuShares'); 76 const reservedMem = sumResources(taskResources, 'Memory.MemoryMB'); 77 const reservedCPU = sumResources(taskResources, 'Cpu.CpuShares'); 78 79 assert.equal( 80 Topology.clusterInfoPanel.memoryProgressValue, 81 reservedMem / totalMem 82 ); 83 assert.equal( 84 Topology.clusterInfoPanel.cpuProgressValue, 85 reservedCPU / totalCPU 86 ); 87 88 assert.equal( 89 Topology.clusterInfoPanel.memoryAbsoluteValue, 90 `${formatBytes(reservedMem * 1024 * 1024)} / ${formatBytes( 91 totalMem * 1024 * 1024 92 )} reserved` 93 ); 94 95 assert.equal( 96 Topology.clusterInfoPanel.cpuAbsoluteValue, 97 `${formatHertz(reservedCPU, 'MHz')} / ${formatHertz( 98 totalCPU, 99 'MHz' 100 )} reserved` 101 ); 102 }); 103 104 test('all allocations for all namespaces and all clients are queried on load', async function (assert) { 105 server.createList('node', 3); 106 server.createList('allocation', 5); 107 108 await Topology.visit(); 109 const requests = this.server.pretender.handledRequests; 110 assert.ok(requests.findBy('url', '/v1/nodes?resources=true')); 111 112 const allocationsRequest = requests.find((req) => 113 req.url.startsWith('/v1/allocations') 114 ); 115 assert.ok(allocationsRequest); 116 117 const allocationRequestParams = queryString.parse( 118 allocationsRequest.url.split('?')[1] 119 ); 120 assert.deepEqual(allocationRequestParams, { 121 namespace: '*', 122 task_states: 'false', 123 resources: 'true', 124 }); 125 }); 126 127 test('when an allocation is selected, the info panel shows information on the allocation', async function (assert) { 128 const nodes = server.createList('node', 5); 129 const job = server.create('job', { createAllocations: false }); 130 const taskGroup = server.schema.find('taskGroup', job.taskGroupIds[0]).name; 131 const allocs = server.createList('allocation', 5, { 132 forceRunningClientStatus: true, 133 jobId: job.id, 134 taskGroup, 135 }); 136 137 // Get the first alloc of the first node that has an alloc 138 const sortedNodes = nodes.sortBy('datacenter'); 139 let node, alloc; 140 for (let n of sortedNodes) { 141 alloc = allocs.find((a) => a.nodeId === n.id); 142 if (alloc) { 143 node = n; 144 break; 145 } 146 } 147 148 const dcIndex = nodes 149 .mapBy('datacenter') 150 .uniq() 151 .sort() 152 .indexOf(node.datacenter); 153 const nodeIndex = nodes 154 .filterBy('datacenter', node.datacenter) 155 .indexOf(node); 156 157 const reset = async () => { 158 await Topology.visit(); 159 await Topology.viz.datacenters[dcIndex].nodes[ 160 nodeIndex 161 ].memoryRects[0].select(); 162 }; 163 164 await reset(); 165 assert.equal(Topology.infoPanelTitle, 'Allocation Details'); 166 167 assert.equal(Topology.allocInfoPanel.id, alloc.id.split('-')[0]); 168 169 const uniqueClients = allocs.mapBy('nodeId').uniq(); 170 assert.equal( 171 Topology.allocInfoPanel.siblingAllocs, 172 `Sibling Allocations: ${allocs.length}` 173 ); 174 assert.equal( 175 Topology.allocInfoPanel.uniquePlacements, 176 `Unique Client Placements: ${uniqueClients.length}` 177 ); 178 179 assert.equal(Topology.allocInfoPanel.job, job.name); 180 assert.ok(Topology.allocInfoPanel.taskGroup.endsWith(alloc.taskGroup)); 181 assert.equal(Topology.allocInfoPanel.client, node.id.split('-')[0]); 182 183 await Topology.allocInfoPanel.visitAlloc(); 184 assert.equal(currentURL(), `/allocations/${alloc.id}`); 185 186 await reset(); 187 188 await Topology.allocInfoPanel.visitJob(); 189 assert.equal(currentURL(), `/jobs/${job.id}@default`); 190 191 await reset(); 192 193 await Topology.allocInfoPanel.visitClient(); 194 assert.equal(currentURL(), `/clients/${node.id}`); 195 }); 196 197 test('changing which allocation is selected changes the metric charts', async function (assert) { 198 server.create('node'); 199 const job1 = server.create('job', { createAllocations: false }); 200 const taskGroup1 = server.schema.find( 201 'taskGroup', 202 job1.taskGroupIds[0] 203 ).name; 204 server.create('allocation', { 205 forceRunningClientStatus: true, 206 jobId: job1.id, 207 taskGroup1, 208 }); 209 210 const job2 = server.create('job', { createAllocations: false }); 211 const taskGroup2 = server.schema.find( 212 'taskGroup', 213 job2.taskGroupIds[0] 214 ).name; 215 server.create('allocation', { 216 forceRunningClientStatus: true, 217 jobId: job2.id, 218 taskGroup2, 219 }); 220 221 await Topology.visit(); 222 await Topology.viz.datacenters[0].nodes[0].memoryRects[0].select(); 223 const firstAllocationTaskNames = 224 Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); 225 226 await Topology.viz.datacenters[0].nodes[0].memoryRects[1].select(); 227 const secondAllocationTaskNames = 228 Topology.allocInfoPanel.charts[0].areas.mapBy('taskName'); 229 230 assert.notDeepEqual(firstAllocationTaskNames, secondAllocationTaskNames); 231 }); 232 233 test('when a node is selected, the info panel shows information on the node', async function (assert) { 234 // A high node count is required for node selection 235 const nodes = server.createList('node', 51); 236 const node = nodes.sortBy('datacenter')[0]; 237 server.createList('allocation', 5, { forceRunningClientStatus: true }); 238 239 const allocs = server.schema.allocations.where({ nodeId: node.id }).models; 240 241 await Topology.visit(); 242 243 await Topology.viz.datacenters[0].nodes[0].selectNode(); 244 assert.equal(Topology.infoPanelTitle, 'Client Details'); 245 246 assert.equal(Topology.nodeInfoPanel.id, node.id.split('-')[0]); 247 assert.equal(Topology.nodeInfoPanel.name, `Name: ${node.name}`); 248 assert.equal(Topology.nodeInfoPanel.address, `Address: ${node.httpAddr}`); 249 assert.equal(Topology.nodeInfoPanel.status, `Status: ${node.status}`); 250 251 assert.equal( 252 Topology.nodeInfoPanel.drainingLabel, 253 node.drain ? 'Yes' : 'No' 254 ); 255 assert.equal( 256 Topology.nodeInfoPanel.eligibleLabel, 257 node.schedulingEligibility === 'eligible' ? 'Yes' : 'No' 258 ); 259 260 assert.equal(Topology.nodeInfoPanel.drainingIsAccented, node.drain); 261 assert.equal( 262 Topology.nodeInfoPanel.eligibleIsAccented, 263 node.schedulingEligibility !== 'eligible' 264 ); 265 266 const taskResources = allocs 267 .mapBy('taskResources.models') 268 .flat() 269 .mapBy('resources'); 270 const reservedMem = sumResources(taskResources, 'Memory.MemoryMB'); 271 const reservedCPU = sumResources(taskResources, 'Cpu.CpuShares'); 272 273 const totalMem = node.nodeResources.Memory.MemoryMB; 274 const totalCPU = node.nodeResources.Cpu.CpuShares; 275 276 assert.equal( 277 Topology.nodeInfoPanel.memoryProgressValue, 278 reservedMem / totalMem 279 ); 280 assert.equal( 281 Topology.nodeInfoPanel.cpuProgressValue, 282 reservedCPU / totalCPU 283 ); 284 285 assert.equal( 286 Topology.nodeInfoPanel.memoryAbsoluteValue, 287 `${formatScheduledBytes( 288 reservedMem * 1024 * 1024 289 )} / ${formatScheduledBytes(totalMem, 'MiB')} reserved` 290 ); 291 292 assert.equal( 293 Topology.nodeInfoPanel.cpuAbsoluteValue, 294 `${formatScheduledHertz(reservedCPU, 'MHz')} / ${formatScheduledHertz( 295 totalCPU, 296 'MHz' 297 )} reserved` 298 ); 299 300 await Topology.nodeInfoPanel.visitNode(); 301 assert.equal(currentURL(), `/clients/${node.id}`); 302 }); 303 304 test('when one or more nodes lack the NodeResources property, a warning message is shown', async function (assert) { 305 server.createList('node', 3); 306 server.createList('allocation', 5); 307 308 server.schema.nodes.all().models[0].update({ nodeResources: null }); 309 310 await Topology.visit(); 311 assert.ok(Topology.filteredNodesWarning.isPresent); 312 assert.ok(Topology.filteredNodesWarning.message.startsWith('1')); 313 }); 314 315 test('Filtering and Querying reduces the number of nodes shown', async function (assert) { 316 server.createList('node', 10); 317 server.createList('node', 2, { 318 nodeClass: 'foo-bar-baz', 319 }); 320 server.createList('allocation', 5); 321 322 await Topology.visit(); 323 assert.dom('[data-test-topo-viz-node]').exists({ count: 12 }); 324 325 await typeIn('input.node-search', server.schema.nodes.first().name); 326 assert.dom('[data-test-topo-viz-node]').exists({ count: 1 }); 327 await typeIn('input.node-search', server.schema.nodes.first().name); 328 assert.dom('[data-test-topo-viz-node]').doesNotExist(); 329 await click('[title="Clear search"]'); 330 assert.dom('[data-test-topo-viz-node]').exists({ count: 12 }); 331 332 await Topology.facets.class.toggle(); 333 await Topology.facets.class.options 334 .findOneBy('label', 'foo-bar-baz') 335 .toggle(); 336 assert.dom('[data-test-topo-viz-node]').exists({ count: 2 }); 337 }); 338 });