Skip to content

Commit 14df887

Browse files
feat: prioritize adopted probes over UUID matches in sync (#756)
1 parent f1e3cfb commit 14df887

File tree

3 files changed

+60
-19
lines changed

3 files changed

+60
-19
lines changed

src/lib/override/adopted-probes.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -366,27 +366,28 @@ export class AdoptedProbes {
366366

367367
/**
368368
* Matches probes with dProbes. Priority order:
369-
* 1. Match by UUID
369+
* 1. Match by UUID + adopted
370370
* 2. Match by IP + adopted
371371
* 3. Match by alt IP + adopted
372-
* 4. Match by IP
373-
* 5. Match by alt IP
372+
* 4. Match by UUID
373+
* 5. Match by IP
374+
* 6. Match by alt IP
374375
*
375-
* This ensures that adopted probes take precedence over non-adopted ones when matching by IP or alt IP.
376+
* This ensures that adopted probes take precedence over non-adopted ones in further logic.
376377
*/
377378
private matchDProbesAndProbes (probes: SocketProbe[]) {
378379
const uuidToProbe = new Map(probes.map(probe => [ probe.uuid, probe ]));
379380
const ipToProbe = new Map(probes.map(probe => [ probe.ipAddress, probe ]));
380381
const altIpToProbe = new Map(probes.map(probe => probe.altIpAddresses.map(altIp => [ altIp, probe ] as const)).flat());
381382
const dProbesWithProbe: { dProbe: DProbe; probe: SocketProbe }[] = [];
382383

383-
// Searching probe for the dProbe by: UUID.
384+
// Searching probe for the dProbe by: adopted UUID.
384385
let dProbesWithoutProbe: DProbe[] = [];
385386

386387
this.dProbes.forEach((dProbe) => {
387388
const probe = dProbe.uuid && uuidToProbe.get(dProbe.uuid);
388389

389-
if (probe) {
390+
if (probe && dProbe.userId) {
390391
dProbesWithProbe.push({ dProbe, probe });
391392
uuidToProbe.delete(probe.uuid);
392393
ipToProbe.delete(probe.ipAddress);
@@ -450,6 +451,23 @@ export class AdoptedProbes {
450451
dProbesWithoutProbe.push(dProbe);
451452
});
452453

454+
// Searching probe for the dProbe by: UUID.
455+
dProbesToCheck = [ ...dProbesWithoutProbe ];
456+
dProbesWithoutProbe = [];
457+
458+
dProbesToCheck.forEach((dProbe) => {
459+
const probe = dProbe.uuid && uuidToProbe.get(dProbe.uuid);
460+
461+
if (probe) {
462+
dProbesWithProbe.push({ dProbe, probe });
463+
uuidToProbe.delete(probe.uuid);
464+
ipToProbe.delete(probe.ipAddress);
465+
probe.altIpAddresses.forEach(altIp => altIpToProbe.delete(altIp));
466+
} else {
467+
dProbesWithoutProbe.push(dProbe);
468+
}
469+
});
470+
453471
// Searching probe for the dProbe by: dProbe IP -> probe IP.
454472
dProbesToCheck = [ ...dProbesWithoutProbe ];
455473
dProbesWithoutProbe = [];
@@ -583,24 +601,25 @@ export class AdoptedProbes {
583601
private findDuplicates (updatedDProbes: DProbe[]) {
584602
const dProbesToDelete: DProbe[] = [];
585603
const dProbeAltIpUpdates: { dProbe: DProbe; update: { altIps: string[] } }[] = [];
604+
const uniqUuids = new Map<string, DProbe>();
586605
const uniqIps = new Map<string, DProbe>();
587606

588607
updatedDProbes.forEach((dProbe) => {
589-
const existingDProbe = uniqIps.get(dProbe.ip);
608+
const existingDProbe = uniqUuids.get(dProbe.uuid) || uniqIps.get(dProbe.ip);
590609

591610
if (
592611
existingDProbe
593612
&& (existingDProbe.userId === dProbe.userId || dProbe.userId === null)
594613
) {
595-
logger.warn(`Duplication found by IP ${dProbe.ip}`, {
614+
logger.warn('Duplication found.', {
596615
stay: _.pick(existingDProbe, [ 'id', 'uuid', 'ip', 'altIps', 'userId' ]),
597616
delete: _.pick(dProbe, [ 'id', 'uuid', 'ip', 'altIps', 'userId' ]),
598617
});
599618

600619
dProbesToDelete.push(dProbe);
601620
return;
602621
} else if (existingDProbe) {
603-
logger.error(`Unremovable duplication found by IP ${dProbe.ip}`, {
622+
logger.error('Unremovable duplication found.', {
604623
stay: _.pick(existingDProbe, [ 'id', 'uuid', 'ip', 'altIps', 'userId' ]),
605624
duplicate: _.pick(dProbe, [ 'id', 'uuid', 'ip', 'altIps', 'userId' ]),
606625
});
@@ -621,6 +640,7 @@ export class AdoptedProbes {
621640
dProbeAltIpUpdates.push({ dProbe, update: { altIps: newAltIps } });
622641
}
623642

643+
uniqUuids.set(dProbe.uuid, dProbe);
624644
uniqIps.set(dProbe.ip, dProbe);
625645
newAltIps.forEach(altIp => uniqIps.set(altIp, dProbe));
626646
});

test/tests/unit/override/adopted-probes.test.ts

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('AdoptedProbes', () => {
88
const defaultAdoption: Row = {
99
id: 'p-1',
1010
name: 'probe-1',
11-
userId: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45',
11+
userId: 'userId',
1212
ip: '1.1.1.1',
1313
altIps: '[]',
1414
uuid: '1-1-1-1-1',
@@ -189,7 +189,7 @@ describe('AdoptedProbes', () => {
189189
expect(sql.insert.callCount).to.equal(0);
190190
});
191191

192-
it('class should match dProbe to probe by: adoption IP -> probe IP', async () => {
192+
it('class should match dProbe to probe by: dProbe IP -> probe IP', async () => {
193193
sql.select.resolves([
194194
{ ...defaultAdoption, uuid: 'unsyncedUuid' },
195195
{ ...defaultAdoption, id: 'p-2', ip: '2.2.2.2', uuid: '2-2-2-2-2', altIps: '[]' },
@@ -214,7 +214,7 @@ describe('AdoptedProbes', () => {
214214
expect(sql.insert.callCount).to.equal(0);
215215
});
216216

217-
it('class should match dProbe to probe by: adoption IP -> probe alt IP', async () => {
217+
it('class should match dProbe to probe by: dProbe IP -> probe alt IP', async () => {
218218
sql.select.resolves([
219219
{ ...defaultAdoption, id: 'p-2', ip: '2.2.2.2', uuid: '2-2-2-2-2', altIps: '[]' },
220220
{ ...defaultAdoption, id: 'p-3', ip: '3.3.3.3', uuid: '3-3-3-3-3', altIps: '["2.2.2.2"]' },
@@ -236,7 +236,7 @@ describe('AdoptedProbes', () => {
236236
expect(sql.insert.callCount).to.equal(0);
237237
});
238238

239-
it('class should match dProbe to probe by: adoption alt IP -> probe IP', async () => {
239+
it('class should match dProbe to probe by: dProbe alt IP -> probe IP', async () => {
240240
sql.select.resolves([
241241
{ ...defaultAdoption, ip: '3.3.3.3', uuid: '3-3-3-3-3', altIps: '["1.1.1.1"]' },
242242
]);
@@ -254,7 +254,7 @@ describe('AdoptedProbes', () => {
254254
expect(sql.insert.callCount).to.equal(0);
255255
});
256256

257-
it('class should match dProbe to probe by: adoption alt IP -> probe alt IP', async () => {
257+
it('class should match dProbe to probe by: dProbe alt IP -> probe alt IP', async () => {
258258
sql.select.resolves([
259259
{ ...defaultAdoption, ip: '3.3.3.3', uuid: '3-3-3-3-3', altIps: '["2.2.2.2"]' },
260260
]);
@@ -272,6 +272,26 @@ describe('AdoptedProbes', () => {
272272
expect(sql.insert.callCount).to.equal(0);
273273
});
274274

275+
it('class should prioritize adopted dProbe match over non-adopted', async () => {
276+
sql.select.resolves([
277+
{ ...defaultAdoption, id: 'p-1', uuid: '1-1-1-1-1', ip: '1.1.1.1', userId: 'userId', status: 'offline' },
278+
{ ...defaultAdoption, id: 'p-2', uuid: '2-2-2-2-2', ip: '2.2.2.2', userId: null, status: 'offline' },
279+
]);
280+
281+
getProbesWithAdminData.returns([{ ...defaultConnectedProbe, uuid: '2-2-2-2-2', ip: '1.1.1.1' }]);
282+
283+
const adoptedProbes = new AdoptedProbes(sqlStub, getProbesWithAdminData);
284+
await adoptedProbes.syncDashboardData();
285+
286+
expect(sql.delete.callCount).to.equal(1);
287+
expect(sql.whereIn.args[0]).to.deep.equal([ 'id', [ 'p-2' ] ]);
288+
expect(sql.where.callCount).to.equal(1);
289+
expect(sql.update.callCount).to.equal(1);
290+
expect(sql.where.args[0]).to.deep.equal([{ id: 'p-1' }]);
291+
expect(sql.update.args[0]).to.deep.equal([{ uuid: '2-2-2-2-2', status: 'ready' }]);
292+
expect(sql.insert.callCount).to.equal(0);
293+
});
294+
275295
it('class should match dProbe to probe by: offline dProbe token+asn+city -> probe token+asn+city', async () => {
276296
sql.select.resolves([{ ...defaultAdoption, status: 'offline', adoptionToken: 'adoptionTokenValue' }]);
277297

@@ -534,13 +554,13 @@ describe('AdoptedProbes', () => {
534554
expect(sql.raw.callCount).to.equal(2);
535555

536556
expect(sql.raw.args[0]![1]).to.deep.equal({
537-
recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45',
557+
recipient: 'userId',
538558
subject: 'Your probe\'s location has changed',
539559
message: 'Globalping detected that your probe [**probe-1**](/probes/p-1) with IP address **1.1.1.1** has changed its location from Ireland to United Kingdom. The custom city value "Dublin" is not applied anymore.\n\nIf this change is not right, please follow the steps in [this issue](https://github.com/jsdelivr/globalping/issues/660).',
540560
});
541561

542562
expect(sql.raw.args[1]![1]).to.deep.equal({
543-
recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45',
563+
recipient: 'userId',
544564
subject: 'Your probe\'s location has changed',
545565
message: 'Globalping detected that your probe [**probe-2**](/probes/p-9) with IP address **9.9.9.9** has changed its location from Ireland to United Kingdom. The custom city value "Dublin" is not applied anymore.\n\nIf this change is not right, please follow the steps in [this issue](https://github.com/jsdelivr/globalping/issues/660).',
546566
});
@@ -658,13 +678,13 @@ describe('AdoptedProbes', () => {
658678
expect(sql.raw.callCount).to.equal(4);
659679

660680
expect(sql.raw.args[2]![1]).to.deep.equal({
661-
recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45',
681+
recipient: 'userId',
662682
subject: 'Your probe\'s location has changed back',
663683
message: 'Globalping detected that your probe [**probe-1**](/probes/p-1) with IP address **1.1.1.1** has changed its location back from United Kingdom to Ireland. The custom city value "Dublin" is now applied again.',
664684
});
665685

666686
expect(sql.raw.args[3]![1]).to.deep.equal({
667-
recipient: '3cff97ae-4a0a-4f34-9f1a-155e6def0a45',
687+
recipient: 'userId',
668688
subject: `Your probe's location has changed back`,
669689
message: 'Globalping detected that your probe [**probe-2**](/probes/p-9) with IP address **9.9.9.9** has changed its location back from United Kingdom to Ireland. The custom city value "Dublin" is now applied again.',
670690
});

wallaby.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ export default function w (wallaby) {
2121
'test/setup.ts',
2222
'test/types.ts',
2323
'package.json',
24-
'knexfile.js',
24+
'knexfile.dashboard.js',
25+
'knexfile.measurement-store-1.js',
2526
'data/*',
2627
],
2728
tests: [

0 commit comments

Comments
 (0)