Test coverage for NodeStatsTracker
This commit is contained in:
parent
fbfc5ecb16
commit
f15e3ba0c2
|
@ -1,3 +1,4 @@
|
||||||
|
import Ember from 'ember';
|
||||||
import { alias } from '@ember/object/computed';
|
import { alias } from '@ember/object/computed';
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
@ -25,10 +26,10 @@ export default Controller.extend(Sortable, {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
pollStats: task(function*() {
|
pollStats: task(function*() {
|
||||||
while (true) {
|
do {
|
||||||
yield this.get('stats').poll();
|
yield this.get('stats').poll();
|
||||||
yield timeout(1000);
|
yield timeout(1000);
|
||||||
}
|
} while (!Ember.testing);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import Ember from 'ember';
|
||||||
import { alias } from '@ember/object/computed';
|
import { alias } from '@ember/object/computed';
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
|
@ -41,10 +42,10 @@ export default Controller.extend(Sortable, Searchable, {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
pollStats: task(function*() {
|
pollStats: task(function*() {
|
||||||
while (true) {
|
do {
|
||||||
yield this.get('stats').poll();
|
yield this.get('stats').poll();
|
||||||
yield timeout(1000);
|
yield timeout(1000);
|
||||||
}
|
} while (!Ember.testing);
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
|
|
|
@ -39,9 +39,11 @@ export default Route.extend(WithWatchers, {
|
||||||
|
|
||||||
watchers: collect('watch', 'watchAllocations'),
|
watchers: collect('watch', 'watchAllocations'),
|
||||||
|
|
||||||
setupController(controller) {
|
setupController(controller, model) {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
controller.get('pollStats').perform();
|
if (model) {
|
||||||
|
controller.get('pollStats').perform();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resetController(controller) {
|
resetController(controller) {
|
||||||
|
|
|
@ -306,7 +306,7 @@ export default function() {
|
||||||
this.get('/client/allocation/:id/stats', clientAllocationStatsHandler);
|
this.get('/client/allocation/:id/stats', clientAllocationStatsHandler);
|
||||||
this.get('/client/fs/logs/:allocation_id', clientAllocationLog);
|
this.get('/client/fs/logs/:allocation_id', clientAllocationLog);
|
||||||
|
|
||||||
this.get('/client/v1/client/stats', function({ clientStats }, { queryParams }) {
|
this.get('/client/stats', function({ clientStats }, { queryParams }) {
|
||||||
return this.serialize(clientStats.find(queryParams.node_id));
|
return this.serialize(clientStats.find(queryParams.node_id));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,10 @@ export default Factory.extend({
|
||||||
node.update({
|
node.update({
|
||||||
eventIds: events.mapBy('id'),
|
eventIds: events.mapBy('id'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.create('client-stats', {
|
||||||
|
id: node.id,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,222 @@
|
||||||
|
import EmberObject from '@ember/object';
|
||||||
|
import { assign } from '@ember/polyfills';
|
||||||
|
import wait from 'ember-test-helpers/wait';
|
||||||
|
import { module, test } from 'ember-qunit';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
import Pretender from 'pretender';
|
||||||
|
import NodeStatsTracker, { stats } from 'nomad-ui/utils/classes/node-stats-tracker';
|
||||||
|
import fetch from 'nomad-ui/utils/fetch';
|
||||||
|
|
||||||
|
module('Unit | Util | NodeStatsTracker');
|
||||||
|
|
||||||
|
const refDate = Date.now();
|
||||||
|
|
||||||
|
const MockNode = overrides =>
|
||||||
|
assign(
|
||||||
|
{
|
||||||
|
id: 'some-identifier',
|
||||||
|
resources: {
|
||||||
|
cpu: 2000,
|
||||||
|
memory: 4096,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
overrides
|
||||||
|
);
|
||||||
|
|
||||||
|
const mockFrame = step => ({
|
||||||
|
CPUTicksConsumed: step + 1000,
|
||||||
|
Memory: {
|
||||||
|
Used: (step + 2048) * 1024 * 1024,
|
||||||
|
},
|
||||||
|
Timestamp: refDate + step,
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the NodeStatsTracker constructor expects a fetch definition and a node', function(assert) {
|
||||||
|
const tracker = NodeStatsTracker.create();
|
||||||
|
assert.throws(
|
||||||
|
() => {
|
||||||
|
tracker.poll();
|
||||||
|
},
|
||||||
|
/StatsTrackers need a fetch method/,
|
||||||
|
'Polling does not work without a fetch method provided'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the url property is computed based off the node id', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const tracker = NodeStatsTracker.create({ fetch, node });
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('url'),
|
||||||
|
`/v1/client/stats?node_id=${node.id}`,
|
||||||
|
'Url is derived from the node id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('reservedCPU and reservedMemory properties come from the node', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const tracker = NodeStatsTracker.create({ fetch, node });
|
||||||
|
|
||||||
|
assert.equal(tracker.get('reservedCPU'), node.resources.cpu, 'reservedCPU comes from the node');
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('reservedMemory'),
|
||||||
|
node.resources.memory,
|
||||||
|
'reservedMemory comes from the node'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('poll results in requesting the url and calling append with the resulting JSON', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const tracker = NodeStatsTracker.create({ fetch, node, append: sinon.spy() });
|
||||||
|
const mockFrame = {
|
||||||
|
Some: {
|
||||||
|
data: ['goes', 'here'],
|
||||||
|
twelve: 12,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const server = new Pretender(function() {
|
||||||
|
this.get('/v1/client/stats', () => [200, {}, JSON.stringify(mockFrame)]);
|
||||||
|
});
|
||||||
|
|
||||||
|
tracker.poll();
|
||||||
|
|
||||||
|
assert.equal(server.handledRequests.length, 1, 'Only one request was made');
|
||||||
|
assert.equal(
|
||||||
|
server.handledRequests[0].url,
|
||||||
|
`/v1/client/stats?node_id=${node.id}`,
|
||||||
|
'The correct URL was requested'
|
||||||
|
);
|
||||||
|
|
||||||
|
return wait().then(() => {
|
||||||
|
assert.ok(
|
||||||
|
tracker.append.calledWith(mockFrame),
|
||||||
|
'The JSON response was passed into append as a POJO'
|
||||||
|
);
|
||||||
|
|
||||||
|
server.shutdown();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('append appropriately maps a data frame to the tracked stats for cpu and memory for the node', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const tracker = NodeStatsTracker.create({ fetch, node });
|
||||||
|
|
||||||
|
assert.deepEqual(tracker.get('cpu'), [], 'No tracked cpu yet');
|
||||||
|
assert.deepEqual(tracker.get('memory'), [], 'No tracked memory yet');
|
||||||
|
|
||||||
|
tracker.append(mockFrame(1));
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
tracker.get('cpu'),
|
||||||
|
[{ timestamp: refDate + 1, used: 1001, percent: 1001 / 2000 }],
|
||||||
|
'One frame of cpu'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
tracker.get('memory'),
|
||||||
|
[{ timestamp: refDate + 1, used: 2049 * 1024 * 1024, percent: 2049 / 4096 }],
|
||||||
|
'One frame of memory'
|
||||||
|
);
|
||||||
|
|
||||||
|
tracker.append(mockFrame(2));
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
tracker.get('cpu'),
|
||||||
|
[
|
||||||
|
{ timestamp: refDate + 1, used: 1001, percent: 1001 / 2000 },
|
||||||
|
{ timestamp: refDate + 2, used: 1002, percent: 1002 / 2000 },
|
||||||
|
],
|
||||||
|
'Two frames of cpu'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
tracker.get('memory'),
|
||||||
|
[
|
||||||
|
{ timestamp: refDate + 1, used: 2049 * 1024 * 1024, percent: 2049 / 4096 },
|
||||||
|
{ timestamp: refDate + 2, used: 2050 * 1024 * 1024, percent: 2050 / 4096 },
|
||||||
|
],
|
||||||
|
'Two frames of memory'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('each stat list has maxLength equal to bufferSize', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const bufferSize = 10;
|
||||||
|
const tracker = NodeStatsTracker.create({ fetch, node, bufferSize });
|
||||||
|
|
||||||
|
for (let i = 1; i <= 20; i++) {
|
||||||
|
tracker.append(mockFrame(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('cpu.length'),
|
||||||
|
bufferSize,
|
||||||
|
`20 calls to append, only ${bufferSize} frames in the stats array`
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('memory.length'),
|
||||||
|
bufferSize,
|
||||||
|
`20 calls to append, only ${bufferSize} frames in the stats array`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('cpu')[0].timestamp,
|
||||||
|
refDate + 11,
|
||||||
|
'Old frames are removed in favor of newer ones'
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
tracker.get('memory')[0].timestamp,
|
||||||
|
refDate + 11,
|
||||||
|
'Old frames are removed in favor of newer ones'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the stats computed property macro constructs a NodeStatsTracker based on a nodeProp and a fetch definition', function(assert) {
|
||||||
|
const node = MockNode();
|
||||||
|
const fetchSpy = sinon.spy();
|
||||||
|
|
||||||
|
const SomeClass = EmberObject.extend({
|
||||||
|
stats: stats('theNode', function() {
|
||||||
|
return () => fetchSpy(this);
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const someObject = SomeClass.create({
|
||||||
|
theNode: node,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
someObject.get('stats.url'),
|
||||||
|
`/v1/client/stats?node_id=${node.id}`,
|
||||||
|
'stats computed property macro creates a NodeStatsTracker'
|
||||||
|
);
|
||||||
|
|
||||||
|
someObject.get('stats').fetch();
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
fetchSpy.calledWith(someObject),
|
||||||
|
'the fetch factory passed into the macro gets called to assign a bound version of fetch to the NodeStatsTracker instance'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('changing the value of the nodeProp constructs a new NodeStatsTracker', function(assert) {
|
||||||
|
const node1 = MockNode();
|
||||||
|
const node2 = MockNode();
|
||||||
|
const SomeClass = EmberObject.extend({
|
||||||
|
stats: stats('theNode', () => fetch),
|
||||||
|
});
|
||||||
|
|
||||||
|
const someObject = SomeClass.create({
|
||||||
|
theNode: node1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stats1 = someObject.get('stats');
|
||||||
|
|
||||||
|
someObject.set('theNode', node2);
|
||||||
|
const stats2 = someObject.get('stats');
|
||||||
|
|
||||||
|
assert.notOk(
|
||||||
|
stats1 === stats2,
|
||||||
|
'Changing the value of the node results in creating a new NodeStatsTracker instance'
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in New Issue