diff --git a/ui/app/components/topo-viz.js b/ui/app/components/topo-viz.js index 18a9c3735..d1254a0dc 100644 --- a/ui/app/components/topo-viz.js +++ b/ui/app/components/topo-viz.js @@ -83,10 +83,18 @@ export default class TopoViz extends Component { const nodes = this.args.nodes; const allocations = this.args.allocations; + // Nodes may not have a resources property due to having an old Nomad agent version. + const badNodes = []; + // Wrap nodes in a topo viz specific data structure and build an index to speed up allocation assignment const nodeContainers = []; const nodeIndex = {}; nodes.forEach(node => { + if (!node.resources) { + badNodes.push(node); + return; + } + const container = this.dataForNode(node); nodeContainers.push(container); nodeIndex[node.id] = container; @@ -99,7 +107,7 @@ export default class TopoViz extends Component { const nodeId = allocation.belongsTo('node').id(); const nodeContainer = nodeIndex[nodeId]; - // Ignore orphaned allocations + // Ignore orphaned allocations and allocations on nodes with an old Nomad agent version. if (!nodeContainer) return; const allocationContainer = this.dataForAllocation(allocation, nodeContainer); @@ -131,6 +139,15 @@ export default class TopoViz extends Component { .domain(extent(nodeContainers.mapBy('memory'))), }; this.topology = topology; + + if (badNodes.length && this.args.onDataError) { + this.args.onDataError([ + { + type: 'filtered-nodes', + context: badNodes, + }, + ]); + } } @action diff --git a/ui/tests/integration/components/topo-viz-test.js b/ui/tests/integration/components/topo-viz-test.js index b60d72cda..76869b98e 100644 --- a/ui/tests/integration/components/topo-viz-test.js +++ b/ui/tests/integration/components/topo-viz-test.js @@ -39,7 +39,8 @@ module('Integration | Component | TopoViz', function(hooks) { @nodes={{this.nodes}} @allocations={{this.allocations}} @onAllocationSelect={{this.onAllocationSelect}} - @onNodeSelect={{this.onNodeSelect}} /> + @onNodeSelect={{this.onNodeSelect}} + @onDataError={{this.onDataError}} /> `; test('presents as a FlexMasonry of datacenters', async function(assert) { @@ -167,4 +168,35 @@ module('Integration | Component | TopoViz', function(hooks) { await TopoViz.datacenters[0].nodes[0].memoryRects[0].select(); assert.equal(TopoViz.allocationAssociations.length, 0); }); + + test('when one or more nodes are missing the resources property, those nodes are filtered out of the topology view and onDataError is called', async function(assert) { + const badNode = node('dc1', 'node0', 1000, 500); + delete badNode.resources; + + this.setProperties({ + nodes: [badNode, node('dc1', 'node1', 1000, 500)], + allocations: [ + alloc('node0', 'job1', 'group', 100, 100), + alloc('node0', 'job1', 'group', 100, 100), + alloc('node1', 'job1', 'group', 100, 100), + alloc('node1', 'job1', 'group', 100, 100), + alloc('node0', 'job1', 'groupTwo', 100, 100), + ], + onNodeSelect: sinon.spy(), + onAllocationSelect: sinon.spy(), + onDataError: sinon.spy(), + }); + + await this.render(commonTemplate); + + assert.ok(this.onDataError.calledOnce); + assert.deepEqual(this.onDataError.getCall(0).args[0], [ + { + type: 'filtered-nodes', + context: [this.nodes[0]], + }, + ]); + + assert.equal(TopoViz.datacenters[0].nodes.length, 1); + }); });