ultravisor-beacon 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -33,8 +33,8 @@ class UltravisorBeaconCapabilityManager
|
|
|
33
33
|
{
|
|
34
34
|
this.log = pLog || {
|
|
35
35
|
info: (...pArgs) => { console.log(...pArgs); },
|
|
36
|
-
warn: (...pArgs) => {
|
|
37
|
-
error: (...pArgs) => {
|
|
36
|
+
warn: (...pArgs) => { console.warn(...pArgs); },
|
|
37
|
+
error: (...pArgs) => { console.error(...pArgs); }
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// Map of Capability name -> descriptor
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
const libHTTP = require('http');
|
|
17
|
+
const libOS = require('os');
|
|
18
|
+
const libFS = require('fs');
|
|
19
|
+
const libPath = require('path');
|
|
20
|
+
const libCrypto = require('crypto');
|
|
17
21
|
|
|
18
22
|
let libWebSocket;
|
|
19
23
|
try
|
|
@@ -433,9 +437,71 @@ class UltravisorBeaconClient
|
|
|
433
437
|
tmpBody.BindAddresses = this._Config.BindAddresses;
|
|
434
438
|
}
|
|
435
439
|
|
|
440
|
+
// Host identity — used by the reachability matrix to detect beacons that
|
|
441
|
+
// live on the same physical machine. Caller can override; default is the
|
|
442
|
+
// node hostname (which inside a container is the container ID).
|
|
443
|
+
tmpBody.HostID = this._Config.HostID || libOS.hostname();
|
|
444
|
+
|
|
445
|
+
// Shared filesystem mounts — each entry tells the coordinator about a
|
|
446
|
+
// local filesystem tree this beacon advertises as accessible. When two
|
|
447
|
+
// beacons report the same MountID, the reachability matrix can pick the
|
|
448
|
+
// "shared-fs" strategy to skip an HTTP file transfer entirely.
|
|
449
|
+
//
|
|
450
|
+
// The MountID derivation includes stat.dev so two beacons that bind-mount
|
|
451
|
+
// the same host directory get the same ID, while two unrelated /media
|
|
452
|
+
// directories on different machines get different IDs.
|
|
453
|
+
tmpBody.SharedMounts = this._normalizeSharedMounts(this._Config.SharedMounts);
|
|
454
|
+
|
|
436
455
|
this._httpRequest('POST', '/Beacon/Register', tmpBody, fCallback);
|
|
437
456
|
}
|
|
438
457
|
|
|
458
|
+
_normalizeSharedMounts(pMounts)
|
|
459
|
+
{
|
|
460
|
+
if (!Array.isArray(pMounts) || pMounts.length === 0)
|
|
461
|
+
{
|
|
462
|
+
return [];
|
|
463
|
+
}
|
|
464
|
+
let tmpResult = [];
|
|
465
|
+
for (let i = 0; i < pMounts.length; i++)
|
|
466
|
+
{
|
|
467
|
+
let tmpEntry = pMounts[i];
|
|
468
|
+
if (!tmpEntry || !tmpEntry.Root)
|
|
469
|
+
{
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
let tmpRoot;
|
|
473
|
+
try
|
|
474
|
+
{
|
|
475
|
+
tmpRoot = libPath.resolve(tmpEntry.Root);
|
|
476
|
+
}
|
|
477
|
+
catch (pError)
|
|
478
|
+
{
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
let tmpMountID = tmpEntry.MountID;
|
|
482
|
+
if (!tmpMountID)
|
|
483
|
+
{
|
|
484
|
+
try
|
|
485
|
+
{
|
|
486
|
+
let tmpStat = libFS.statSync(tmpRoot);
|
|
487
|
+
tmpMountID = libCrypto.createHash('sha256')
|
|
488
|
+
.update(tmpStat.dev + ':' + tmpRoot)
|
|
489
|
+
.digest('hex').substring(0, 16);
|
|
490
|
+
}
|
|
491
|
+
catch (pError)
|
|
492
|
+
{
|
|
493
|
+
// Mount root does not exist on this beacon — skip it.
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
tmpResult.push({
|
|
498
|
+
MountID: tmpMountID,
|
|
499
|
+
Root: tmpRoot
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return tmpResult;
|
|
503
|
+
}
|
|
504
|
+
|
|
439
505
|
_deregister(fCallback)
|
|
440
506
|
{
|
|
441
507
|
this._httpRequest('DELETE', `/Beacon/${this._BeaconID}`, null, fCallback);
|
|
@@ -51,7 +51,9 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
51
51
|
HeartbeatIntervalMs: 30000,
|
|
52
52
|
StagingPath: '',
|
|
53
53
|
Tags: {},
|
|
54
|
-
Contexts: {}
|
|
54
|
+
Contexts: {},
|
|
55
|
+
HostID: '',
|
|
56
|
+
SharedMounts: []
|
|
55
57
|
}, this.options || {});
|
|
56
58
|
|
|
57
59
|
// Internal components
|
|
@@ -259,6 +261,18 @@ class UltravisorBeaconService extends libFableServiceBase
|
|
|
259
261
|
Providers: []
|
|
260
262
|
});
|
|
261
263
|
|
|
264
|
+
// Shared-fs identity (forwarded to the thin client which sends it to the
|
|
265
|
+
// coordinator at registration time so the reachability matrix can detect
|
|
266
|
+
// beacons that share a filesystem on the same host).
|
|
267
|
+
if (this.options.HostID)
|
|
268
|
+
{
|
|
269
|
+
tmpClientConfig.HostID = this.options.HostID;
|
|
270
|
+
}
|
|
271
|
+
if (Array.isArray(this.options.SharedMounts) && this.options.SharedMounts.length > 0)
|
|
272
|
+
{
|
|
273
|
+
tmpClientConfig.SharedMounts = this.options.SharedMounts;
|
|
274
|
+
}
|
|
275
|
+
|
|
262
276
|
// Create thin client
|
|
263
277
|
this._ThinClient = new libBeaconClient(tmpClientConfig);
|
|
264
278
|
|
|
@@ -10,12 +10,17 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
const libAssert = require('assert');
|
|
13
|
+
const libFS = require('fs');
|
|
14
|
+
const libPath = require('path');
|
|
15
|
+
const libCrypto = require('crypto');
|
|
16
|
+
const libOS = require('os');
|
|
13
17
|
|
|
14
18
|
const libCapabilityProvider = require('../source/Ultravisor-Beacon-CapabilityProvider.cjs');
|
|
15
19
|
const libCapabilityAdapter = require('../source/Ultravisor-Beacon-CapabilityAdapter.cjs');
|
|
16
20
|
const libCapabilityManager = require('../source/Ultravisor-Beacon-CapabilityManager.cjs');
|
|
17
21
|
const libConnectivityHTTP = require('../source/Ultravisor-Beacon-ConnectivityHTTP.cjs');
|
|
18
22
|
const libProviderRegistry = require('../source/Ultravisor-Beacon-ProviderRegistry.cjs');
|
|
23
|
+
const libBeaconClient = require('../source/Ultravisor-Beacon-Client.cjs');
|
|
19
24
|
|
|
20
25
|
// We can require the service without Fable for standalone testing
|
|
21
26
|
const libBeaconService = require('../source/Ultravisor-Beacon-Service.cjs');
|
|
@@ -604,5 +609,136 @@ suite
|
|
|
604
609
|
);
|
|
605
610
|
}
|
|
606
611
|
);
|
|
612
|
+
|
|
613
|
+
// ============================================================
|
|
614
|
+
// Shared-FS Reachability — SharedMounts normalization
|
|
615
|
+
// ============================================================
|
|
616
|
+
suite
|
|
617
|
+
(
|
|
618
|
+
'SharedMounts Normalization',
|
|
619
|
+
function ()
|
|
620
|
+
{
|
|
621
|
+
test
|
|
622
|
+
(
|
|
623
|
+
'Should return empty array for missing/empty input',
|
|
624
|
+
function ()
|
|
625
|
+
{
|
|
626
|
+
let tmpClient = new libBeaconClient({ Name: 'mt-test', Capabilities: [] });
|
|
627
|
+
libAssert.deepStrictEqual(tmpClient._normalizeSharedMounts(undefined), []);
|
|
628
|
+
libAssert.deepStrictEqual(tmpClient._normalizeSharedMounts(null), []);
|
|
629
|
+
libAssert.deepStrictEqual(tmpClient._normalizeSharedMounts([]), []);
|
|
630
|
+
libAssert.deepStrictEqual(tmpClient._normalizeSharedMounts('not-an-array'), []);
|
|
631
|
+
}
|
|
632
|
+
);
|
|
633
|
+
|
|
634
|
+
test
|
|
635
|
+
(
|
|
636
|
+
'Should auto-derive MountID from stat.dev + path',
|
|
637
|
+
function ()
|
|
638
|
+
{
|
|
639
|
+
let tmpClient = new libBeaconClient({ Name: 'mt-test', Capabilities: [] });
|
|
640
|
+
let tmpRoot = libOS.tmpdir();
|
|
641
|
+
let tmpResolved = libPath.resolve(tmpRoot);
|
|
642
|
+
let tmpExpected = libCrypto.createHash('sha256')
|
|
643
|
+
.update(libFS.statSync(tmpResolved).dev + ':' + tmpResolved)
|
|
644
|
+
.digest('hex').substring(0, 16);
|
|
645
|
+
|
|
646
|
+
let tmpResult = tmpClient._normalizeSharedMounts([{ Root: tmpRoot }]);
|
|
647
|
+
libAssert.strictEqual(tmpResult.length, 1);
|
|
648
|
+
libAssert.strictEqual(tmpResult[0].MountID, tmpExpected);
|
|
649
|
+
libAssert.strictEqual(tmpResult[0].Root, tmpResolved);
|
|
650
|
+
}
|
|
651
|
+
);
|
|
652
|
+
|
|
653
|
+
test
|
|
654
|
+
(
|
|
655
|
+
'Should preserve explicit MountID without stat',
|
|
656
|
+
function ()
|
|
657
|
+
{
|
|
658
|
+
let tmpClient = new libBeaconClient({ Name: 'mt-test', Capabilities: [] });
|
|
659
|
+
let tmpResult = tmpClient._normalizeSharedMounts(
|
|
660
|
+
[{ MountID: 'manual-id-1234', Root: libOS.tmpdir() }]);
|
|
661
|
+
libAssert.strictEqual(tmpResult.length, 1);
|
|
662
|
+
libAssert.strictEqual(tmpResult[0].MountID, 'manual-id-1234');
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
test
|
|
667
|
+
(
|
|
668
|
+
'Should skip entries without a Root',
|
|
669
|
+
function ()
|
|
670
|
+
{
|
|
671
|
+
let tmpClient = new libBeaconClient({ Name: 'mt-test', Capabilities: [] });
|
|
672
|
+
let tmpResult = tmpClient._normalizeSharedMounts(
|
|
673
|
+
[{ MountID: 'no-root' }, { Root: libOS.tmpdir() }, null, undefined]);
|
|
674
|
+
libAssert.strictEqual(tmpResult.length, 1);
|
|
675
|
+
libAssert.strictEqual(tmpResult[0].Root, libPath.resolve(libOS.tmpdir()));
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
test
|
|
680
|
+
(
|
|
681
|
+
'Should skip entries whose Root does not exist on this beacon',
|
|
682
|
+
function ()
|
|
683
|
+
{
|
|
684
|
+
let tmpClient = new libBeaconClient({ Name: 'mt-test', Capabilities: [] });
|
|
685
|
+
let tmpResult = tmpClient._normalizeSharedMounts(
|
|
686
|
+
[{ Root: '/nonexistent-path-12345-xyz' }]);
|
|
687
|
+
libAssert.deepStrictEqual(tmpResult, []);
|
|
688
|
+
}
|
|
689
|
+
);
|
|
690
|
+
|
|
691
|
+
test
|
|
692
|
+
(
|
|
693
|
+
'Two beacons that point at the same Root should derive identical MountIDs',
|
|
694
|
+
function ()
|
|
695
|
+
{
|
|
696
|
+
let tmpClientA = new libBeaconClient({ Name: 'mt-a', Capabilities: [] });
|
|
697
|
+
let tmpClientB = new libBeaconClient({ Name: 'mt-b', Capabilities: [] });
|
|
698
|
+
let tmpA = tmpClientA._normalizeSharedMounts([{ Root: libOS.tmpdir() }]);
|
|
699
|
+
let tmpB = tmpClientB._normalizeSharedMounts([{ Root: libOS.tmpdir() }]);
|
|
700
|
+
libAssert.strictEqual(tmpA[0].MountID, tmpB[0].MountID);
|
|
701
|
+
}
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
// ============================================================
|
|
707
|
+
// BeaconService — HostID and SharedMounts forwarding
|
|
708
|
+
// ============================================================
|
|
709
|
+
suite
|
|
710
|
+
(
|
|
711
|
+
'Service forwards HostID and SharedMounts',
|
|
712
|
+
function ()
|
|
713
|
+
{
|
|
714
|
+
test
|
|
715
|
+
(
|
|
716
|
+
'Service options should accept HostID and SharedMounts with sensible defaults',
|
|
717
|
+
function ()
|
|
718
|
+
{
|
|
719
|
+
let tmpService = new libBeaconService({ Name: 'fwd-test' });
|
|
720
|
+
// Defaults from Object.assign should land
|
|
721
|
+
libAssert.strictEqual(tmpService.options.HostID, '');
|
|
722
|
+
libAssert.deepStrictEqual(tmpService.options.SharedMounts, []);
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
test
|
|
727
|
+
(
|
|
728
|
+
'Service options should preserve caller-supplied HostID and SharedMounts',
|
|
729
|
+
function ()
|
|
730
|
+
{
|
|
731
|
+
let tmpService = new libBeaconService({
|
|
732
|
+
Name: 'fwd-test',
|
|
733
|
+
HostID: 'custom-host-id',
|
|
734
|
+
SharedMounts: [{ MountID: 'abc123', Root: '/data' }]
|
|
735
|
+
});
|
|
736
|
+
libAssert.strictEqual(tmpService.options.HostID, 'custom-host-id');
|
|
737
|
+
libAssert.deepStrictEqual(tmpService.options.SharedMounts,
|
|
738
|
+
[{ MountID: 'abc123', Root: '/data' }]);
|
|
739
|
+
}
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
);
|
|
607
743
|
}
|
|
608
744
|
);
|