Skip to content

Commit

Permalink
feat: update adopted-probes class
Browse files Browse the repository at this point in the history
  • Loading branch information
alexey-yarmosh committed Oct 6, 2023
1 parent 00ca77f commit 1276e52
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 61 deletions.
86 changes: 69 additions & 17 deletions src/lib/adopted-probes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Knex } from 'knex';
import { scopedLogger } from './logger.js';
import { client } from './sql/client.js';
import { fetchSockets } from './ws/server.js';

const logger = scopedLogger('adopted-probes');

Expand All @@ -9,14 +10,18 @@ const TABLE_NAME = 'adopted_probes';
type AdoptedProbe = {
ip: string;
uuid: string;
city: string;
lastSyncDate: string;
}

export class AdoptedProbes {
private adoptedProbesByIp: Map<AdoptedProbe['ip'], Omit<AdoptedProbe, 'ip'>> = new Map();

Check failure on line 17 in src/lib/adopted-probes.ts

View workflow job for this annotation

GitHub Actions / build

'adoptedProbesByIp' is declared but its value is never read.
private adoptedProbesByUuid: Map<AdoptedProbe['uuid'], Omit<AdoptedProbe, 'uuid'>> = new Map();
private connectedIpToUuid: Map<string, string> = new Map();
private connectedUuidToIp: Map<string, string> = new Map();

constructor (private readonly sql: Knex) {}
constructor (
private readonly sql: Knex,
private readonly fetchWsSockets: typeof fetchSockets,
) {}

scheduleSync () {
setTimeout(() => {
Expand All @@ -26,20 +31,50 @@ export class AdoptedProbes {
}, 5000);
}

async syncProbeIds (probeIp: string, probeUuid: string) {
const adoptedProbeByIp = this.adoptedProbesByIp.get(probeIp);
private async syncDashboardData () {
const allSockets = await this.fetchWsSockets();
this.connectedIpToUuid = new Map(allSockets.map(socket => [ socket.data.probe.ipAddress, socket.data.probe.uuid ]));
this.connectedUuidToIp = new Map(allSockets.map(socket => [ socket.data.probe.uuid, socket.data.probe.ipAddress ]));

const adoptedProbes = await this.sql(TABLE_NAME).select<AdoptedProbe[]>('ip', 'uuid', 'lastSyncDate');
this.adoptedProbesByIp = new Map(adoptedProbes.map(({ ip, uuid, lastSyncDate }) => [ ip, { uuid, lastSyncDate }]));
await Promise.all(adoptedProbes.map(({ ip, uuid }) => this.syncProbeIds(ip, uuid)));
await Promise.all(adoptedProbes.map(({ ip, lastSyncDate }) => this.updateSyncDate(ip, lastSyncDate)));
}

private async syncProbeIds (ip: string, uuid: string) {
const connectedUuid = this.connectedIpToUuid.get(ip);

if (adoptedProbeByIp && adoptedProbeByIp.uuid === probeUuid) { // Probe ids are synced
if (connectedUuid && connectedUuid === uuid) { // ip and uuid are synced
return;
} else if (adoptedProbeByIp) { // Uuid is wrong
await this.updateUuid(probeIp, probeUuid);
}

if (connectedUuid && connectedUuid !== uuid) { // uuid was found, but it is outdated
await this.updateUuid(ip, connectedUuid);
return;
}

const adoptedProbeByUuid = this.adoptedProbesByUuid.get(probeUuid);
const connectedIp = this.connectedUuidToIp.get(uuid);

if (adoptedProbeByUuid) { // Probe not found by ip but found by uuid => ip is wrong
await this.updateIp(probeIp, probeUuid);
if (connectedIp) { // data was found by uuid, but not found by ip, therefore ip is outdated
await this.updateIp(connectedIp, uuid);
}
}

private async updateSyncDate (ip: string, lastSyncDate: string) {
if (this.isToday(lastSyncDate)) {
return;
}

const probeIsConnected = this.connectedIpToUuid.has(ip);

if (probeIsConnected) {
await this.updateLastSyncDate(ip);
return;
}

if (this.isMoreThan30DaysAgo(lastSyncDate)) {
await this.deleteAdoptedProbe(ip);
}
}

Expand All @@ -51,12 +86,29 @@ export class AdoptedProbes {
await this.sql(TABLE_NAME).where({ uuid }).update({ ip });
}

private async syncDashboardData () {
const probes = await this.sql(TABLE_NAME).select<AdoptedProbe[]>('ip', 'uuid');
// Storing city as emtpy string until https://github.com/jsdelivr/globalping/issues/427 is implemented
this.adoptedProbesByIp = new Map(probes.map(probe => [ probe.ip, { uuid: probe.uuid, city: '' }]));
this.adoptedProbesByUuid = new Map(probes.map(probe => [ probe.uuid, { ip: probe.ip, city: '' }]));
private async updateLastSyncDate (ip: string) {
await this.sql(TABLE_NAME).where({ ip }).update({ lastSyncDate: new Date() });
}

private async deleteAdoptedProbe (ip: string) {
await this.sql(TABLE_NAME).where({ ip }).delete();
}

private isToday (dateString: string) {
const currentDate = new Date();
const currentDateString = currentDate.toISOString().split('T')[0];
return dateString === currentDateString;
}

private isMoreThan30DaysAgo (dateString: string) {
const inputDate = new Date(dateString);
const currentDate = new Date();

const timeDifference = currentDate.getTime() - inputDate.getTime();
const daysDifference = timeDifference / (24 * 3600 * 1000);

return daysDifference > 30;
}
}

export const adoptedProbes = new AdoptedProbes(client);
export const adoptedProbes = new AdoptedProbes(client, fetchSockets);
131 changes: 87 additions & 44 deletions test/tests/unit/adopted-probes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import { AdoptedProbes } from '../../../src/lib/adopted-probes.js';

const selectStub = sinon.stub();
const updateStub = sinon.stub();
const deleteStub = sinon.stub();
const whereStub = sinon.stub().returns({
update: updateStub,
delete: deleteStub,
});
const sqlStub = sinon.stub().returns({
select: selectStub,
where: whereStub,
});
const fetchSocketsStub = sinon.stub().resolves([]);
let sandbox: sinon.SinonSandbox;

describe('AdoptedProbes', () => {
Expand All @@ -25,88 +28,128 @@ describe('AdoptedProbes', () => {
});

it('startSync method should sync the data and start regular syncs', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1970-01-01' }]);

adoptedProbes.scheduleSync();

expect(sqlStub.callCount).to.equal(0);
expect(selectStub.callCount).to.equal(0);
await sandbox.clock.tickAsync(5500);
await sandbox.clock.tickAsync(5000);
expect(sqlStub.callCount).to.equal(1);
expect(sqlStub.args[0]).deep.equal([ 'adopted_probes' ]);
expect(selectStub.callCount).to.equal(1);
expect(selectStub.args[0]).deep.equal([ 'ip', 'uuid' ]);
expect(selectStub.args[0]).deep.equal([ 'ip', 'uuid', 'lastSyncDate' ]);
});

it('syncProbeIds method should do nothing if probe was not found either by ip or uuid', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);
it('class should update uuid if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1970-01-01' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '1.1.1.1', uuid: '2-2-2-2-2' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5500);
await sandbox.clock.tickAsync(5000);

await adoptedProbes.syncProbeIds('2.2.2.2', '2-2-2-2-2');
expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ ip: '1.1.1.1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ uuid: '2-2-2-2-2' }]);
});

it('class should update ip if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1970-01-01' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '2.2.2.2', uuid: '1-1-1-1-1' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ uuid: '1-1-1-1-1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ ip: '2.2.2.2' }]);
});

it('class should do nothing if adopted probe was not found and lastSyncDate < 30 days away', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1969-12-15' }]);
fetchSocketsStub.resolves([]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(0);
expect(updateStub.callCount).to.equal(0);
expect(deleteStub.callCount).to.equal(0);
});

it('syncProbeIds method should do nothing if probe data is actual', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);
it('class should do nothing if adopted probe was not found and lastSyncDate > 30 days away', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1969-11-15' }]);
fetchSocketsStub.resolves([]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5500);
await sandbox.clock.tickAsync(5000);

await adoptedProbes.syncProbeIds('1.1.1.1', '1-1-1-1-1');
expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ ip: '1.1.1.1' }]);
expect(updateStub.callCount).to.equal(0);
expect(deleteStub.callCount).to.equal(1);
expect(deleteStub.callCount).to.equal(1);
});

it('class should do nothing if probe data is actual', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1970-01-01' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '1.1.1.1', uuid: '1-1-1-1-1' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(0);
expect(updateStub.callCount).to.equal(0);
});

it('syncProbeIds method should update uuid if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);
it('class update lastSyncDate if probe is connected and lastSyncDate < 30 days away', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1969-12-31' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '1.1.1.1', uuid: '1-1-1-1-1' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5500);

await adoptedProbes.syncProbeIds('1.1.1.1', '2-2-2-2-2');
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ ip: '1.1.1.1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ uuid: '2-2-2-2-2' }]);
expect(updateStub.args[0]).to.deep.equal([{ lastSyncDate: new Date() }]);
expect(deleteStub.callCount).to.equal(0);
});

it('syncProbeIds method should update ip if it is wrong', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex);
selectStub.resolves([{
ip: '1.1.1.1',
uuid: '1-1-1-1-1',
}]);
it('class update lastSyncDate if probe is connected and lastSyncDate > 30 days away', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1969-11-15' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '1.1.1.1', uuid: '1-1-1-1-1' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5500);

await adoptedProbes.syncProbeIds('2.2.2.2', '1-1-1-1-1');
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(1);
expect(whereStub.args[0]).to.deep.equal([{ uuid: '1-1-1-1-1' }]);
expect(whereStub.args[0]).to.deep.equal([{ ip: '1.1.1.1' }]);
expect(updateStub.callCount).to.equal(1);
expect(updateStub.args[0]).to.deep.equal([{ ip: '2.2.2.2' }]);
expect(updateStub.args[0]).to.deep.equal([{ lastSyncDate: new Date() }]);
expect(deleteStub.callCount).to.equal(0);
});

it('class update lastSyncDate should not update anything if lastSyncDate is today', async () => {
const adoptedProbes = new AdoptedProbes(sqlStub as unknown as Knex, fetchSocketsStub);
selectStub.resolves([{ ip: '1.1.1.1', uuid: '1-1-1-1-1', lastSyncDate: '1970-01-01' }]);
fetchSocketsStub.resolves([{ data: { probe: { ipAddress: '1.1.1.1', uuid: '1-1-1-1-1' } } }]);

adoptedProbes.scheduleSync();
await sandbox.clock.tickAsync(5000);

expect(whereStub.callCount).to.equal(0);
expect(updateStub.callCount).to.equal(0);
expect(deleteStub.callCount).to.equal(0);
});
});

0 comments on commit 1276e52

Please sign in to comment.