Add Gateway traffic totals and dashboard chart
This commit is contained in:
@@ -196,6 +196,15 @@ test('device traffic totals persist exact deltas across polls and process epochs
|
||||
error: null,
|
||||
proxy: { lastObservedAt: observedAt, error: null },
|
||||
});
|
||||
assert.deepEqual(snapshot.traffic, {
|
||||
gatewayBytes: '9007199254741093',
|
||||
proxyBytes: '3000',
|
||||
totalBytes: '9007199254744093',
|
||||
gatewayObservedAt: observedAt,
|
||||
proxyObservedAt: observedAt,
|
||||
observedAt,
|
||||
history: [],
|
||||
});
|
||||
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices[0].uploadBytes, '9007199254740993');
|
||||
@@ -216,6 +225,8 @@ test('device traffic totals persist exact deltas across polls and process epochs
|
||||
assert.equal(snapshot.devices[0].downloadBytes, '150');
|
||||
assert.equal(snapshot.devices[0].proxyUploadBytes, '1010');
|
||||
assert.equal(snapshot.devices[0].proxyDownloadBytes, '2050');
|
||||
assert.equal(snapshot.traffic.gatewayBytes, '9007199254741145');
|
||||
assert.equal(snapshot.traffic.proxyBytes, '3060');
|
||||
|
||||
service = createService();
|
||||
snapshot = await service.refresh();
|
||||
@@ -239,6 +250,7 @@ test('device traffic totals persist exact deltas across polls and process epochs
|
||||
assert.equal(snapshot.devices[0].downloadBytes, '170');
|
||||
assert.equal(snapshot.devices[0].proxyUploadBytes, '1013');
|
||||
assert.equal(snapshot.devices[0].proxyDownloadBytes, '2054');
|
||||
assert.equal(snapshot.traffic.totalBytes, '9007199254744242');
|
||||
|
||||
traffic = {
|
||||
...traffic,
|
||||
@@ -250,6 +262,7 @@ test('device traffic totals persist exact deltas across polls and process epochs
|
||||
|
||||
traffic = {
|
||||
...traffic,
|
||||
observedAt: '2026-08-07T12:00:15.000Z',
|
||||
devices: [{
|
||||
...traffic.devices[0],
|
||||
uploadBytes: '12',
|
||||
@@ -265,12 +278,17 @@ test('device traffic totals persist exact deltas across polls and process epochs
|
||||
assert.equal(snapshot.devices[0].proxyDownloadBytes, '2054');
|
||||
assert.equal(snapshot.source.traffic.error, null);
|
||||
assert.match(snapshot.source.traffic.proxy.error, /уменьшился/);
|
||||
assert.equal(snapshot.traffic.proxyBytes, '3067');
|
||||
assert.equal(snapshot.traffic.gatewayObservedAt, '2026-08-07T12:00:15.000Z');
|
||||
assert.equal(snapshot.traffic.proxyObservedAt, observedAt);
|
||||
assert.equal(snapshot.traffic.observedAt, observedAt);
|
||||
|
||||
trafficError = new Error('traffic unavailable');
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices[0].uploadBytes, '9007199254741007');
|
||||
assert.equal(snapshot.source.traffic.lastObservedAt, observedAt);
|
||||
assert.equal(snapshot.source.traffic.lastObservedAt, '2026-08-07T12:00:15.000Z');
|
||||
assert.equal(snapshot.source.traffic.error, 'traffic unavailable');
|
||||
assert.equal(snapshot.traffic.totalBytes, '9007199254744249');
|
||||
});
|
||||
|
||||
test('device traffic history stays in bounded service memory and survives client snapshots', async (t) => {
|
||||
@@ -313,9 +331,55 @@ test('device traffic history stays in bounded service memory and survives client
|
||||
gatewayBytes: '30',
|
||||
proxyBytes: '5',
|
||||
}]);
|
||||
assert.deepEqual(snapshot.traffic.history, [{
|
||||
observedAt,
|
||||
gatewayBytes: '30',
|
||||
proxyBytes: '5',
|
||||
}]);
|
||||
assert.deepEqual(service.snapshot().devices[0].trafficHistory, snapshot.devices[0].trafficHistory);
|
||||
assert.doesNotMatch(fs.readFileSync(filePath, 'utf8'), /trafficHistory/);
|
||||
assert.deepEqual(createService().snapshot().devices[0].trafficHistory, []);
|
||||
assert.deepEqual(createService().snapshot().traffic.history, []);
|
||||
});
|
||||
|
||||
test('global traffic stays monotonic when a device expires and returns in the same epoch', async (t) => {
|
||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'harbor-global-traffic-retention-'));
|
||||
t.after(() => fs.rmSync(directory, { recursive: true, force: true }));
|
||||
const store = createJsonStore({
|
||||
filePath: path.join(directory, 'devices.json'),
|
||||
defaultValue: {},
|
||||
migrate: migrateDeviceInventoryState,
|
||||
});
|
||||
const mac = '00:11:22:33:44:55';
|
||||
let observedAt = '2026-07-01T12:00:00.000Z';
|
||||
let present = true;
|
||||
let uploadBytes = '100';
|
||||
const service = createDeviceInventoryService({
|
||||
store,
|
||||
observe: () => ({
|
||||
observedAt,
|
||||
error: null,
|
||||
observations: present
|
||||
? [{ ip: '192.168.50.7', mac, interface: 'eth0', observedAt, active: true }]
|
||||
: [],
|
||||
}),
|
||||
observeTraffic: () => ({
|
||||
epoch: 'epoch-a', generation: 'rules-a', observedAt, source: { error: null },
|
||||
devices: [{ mac, uploadBytes, downloadBytes: '0' }],
|
||||
}),
|
||||
});
|
||||
|
||||
assert.equal((await service.refresh()).traffic.totalBytes, '100');
|
||||
present = false;
|
||||
observedAt = '2026-08-07T12:00:00.000Z';
|
||||
let snapshot = await service.refresh();
|
||||
assert.equal(snapshot.devices.length, 0);
|
||||
assert.equal(snapshot.traffic.totalBytes, '100');
|
||||
present = true;
|
||||
snapshot = await service.refresh();
|
||||
assert.equal(snapshot.traffic.totalBytes, '100');
|
||||
uploadBytes = '110';
|
||||
assert.equal((await service.refresh()).traffic.totalBytes, '110');
|
||||
});
|
||||
|
||||
test('legacy dataplane samples preserve saved proxy totals while Gateway totals keep advancing', async (t) => {
|
||||
@@ -462,7 +526,7 @@ test('device policy is independent from pinning, persists, and keeps the last ap
|
||||
assert.equal(snapshot.devices[0].desiredPolicy, 'direct');
|
||||
assert.equal(snapshot.devices[0].appliedPolicy, 'direct');
|
||||
assert.equal(snapshot.devices[0].policyStatus, 'applied');
|
||||
assert.equal(store.read().schemaVersion, 2);
|
||||
assert.equal(store.read().schemaVersion, DEVICE_INVENTORY_SCHEMA_VERSION);
|
||||
assert.equal(store.read().policy.schemaVersion, 1);
|
||||
|
||||
service = createService();
|
||||
@@ -585,7 +649,7 @@ test('device inventory backs up and normalizes a malformed additive policy check
|
||||
});
|
||||
|
||||
const migrated = store.read();
|
||||
assert.equal(migrated.schemaVersion, 2);
|
||||
assert.equal(migrated.schemaVersion, DEVICE_INVENTORY_SCHEMA_VERSION);
|
||||
assert.equal(migrated.policy.schemaVersion, 1);
|
||||
assert.equal(migrated.policy.dataplaneEpoch, null);
|
||||
assert.deepEqual(migrated.policy.byMac, {});
|
||||
@@ -666,6 +730,12 @@ test('device inventory backs up malformed v2 traffic and re-baselines without do
|
||||
new Set(migrated.traffic.rebaselineMacs),
|
||||
new Set([firstMac, secondMac, missingBaselineMac, missingTotalMac, expiredMac]),
|
||||
);
|
||||
assert.equal(migrated.traffic.global.gateway.uploadBytes, '1300');
|
||||
assert.equal(migrated.traffic.global.gateway.downloadBytes, '1500');
|
||||
assert.deepEqual(
|
||||
new Set(migrated.traffic.global.gateway.rebaselineMacs),
|
||||
new Set([firstMac, secondMac, missingBaselineMac, missingTotalMac, expiredMac]),
|
||||
);
|
||||
|
||||
let counters = [
|
||||
{ mac: firstMac, uploadBytes: '200', downloadBytes: '300' },
|
||||
@@ -703,6 +773,7 @@ test('device inventory backs up malformed v2 traffic and re-baselines without do
|
||||
assert.equal(byMac.get(missingBaselineMac).uploadBytes, '800');
|
||||
assert.equal(byMac.get(missingTotalMac).uploadBytes, '0');
|
||||
assert.equal(byMac.has(expiredMac), false);
|
||||
assert.equal(snapshot.traffic.totalBytes, '2800');
|
||||
assert.deepEqual(store.read().traffic.rebaselineMacs, []);
|
||||
assert.equal(snapshot.source.traffic.error, null);
|
||||
counters = [
|
||||
@@ -721,6 +792,7 @@ test('device inventory backs up malformed v2 traffic and re-baselines without do
|
||||
assert.equal(byMac.get(missingBaselineMac).downloadBytes, '905');
|
||||
assert.equal(byMac.get(missingTotalMac).uploadBytes, '20');
|
||||
assert.equal(byMac.get(missingTotalMac).downloadBytes, '20');
|
||||
assert.equal(snapshot.traffic.totalBytes, '2945');
|
||||
});
|
||||
|
||||
test('malformed proxy totals are backed up and an expired recovery marker is cleared', async (t) => {
|
||||
|
||||
Reference in New Issue
Block a user