@oneuptime/common 12.0.34 → 13.0.0
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/Models/DatabaseModels/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import NetworkDeviceMacLearningUtil, {
|
|
3
|
+
MacLearningResult,
|
|
4
|
+
} from "../../../../Server/Utils/Monitor/NetworkDeviceMacLearningUtil";
|
|
5
|
+
import NetworkDeviceService from "../../../../Server/Services/NetworkDeviceService";
|
|
6
|
+
import NetworkDevice from "../../../../Models/DatabaseModels/NetworkDevice";
|
|
7
|
+
import { MacLearningIpBinding } from "../../../../Utils/Monitor/DeviceMacLearningUtil";
|
|
8
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
9
|
+
import LIMIT_MAX from "../../../../Types/Database/LimitMax";
|
|
10
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
11
|
+
import { FindOperator } from "typeorm";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* NetworkDeviceMacLearningUtil is the thin server half of MAC learning: it
|
|
15
|
+
* reads the devices registered at the addresses a router's ARP table bound,
|
|
16
|
+
* hands them to the pure planner, and applies the plan with the hook-free
|
|
17
|
+
* compare-and-set write. The planner's rules are pinned in
|
|
18
|
+
* Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts; what is pinned HERE is
|
|
19
|
+
* the database contract around it:
|
|
20
|
+
*
|
|
21
|
+
* - the read asks about exactly the bound addresses, chunked so the
|
|
22
|
+
* literal `IN (...)` list never exceeds what the planner can take;
|
|
23
|
+
* - the read is NOT narrowed by site or by an empty MAC column - the
|
|
24
|
+
* planner has to see the second device at an address, or the device that
|
|
25
|
+
* already has a MAC, to refuse correctly;
|
|
26
|
+
* - the write is one hook-free statement per planned device, guarded so it
|
|
27
|
+
* fills the column only while it is still NULL.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
const PROJECT_ID: string = "1c9d4a7b-0000-4000-8000-000000000011";
|
|
31
|
+
const ROUTER_ID: string = "8f2c1f0e-0000-4000-8000-0000000000aa";
|
|
32
|
+
const REGISTER_ID: string = "8f2c1f0e-0000-4000-8000-0000000000bb";
|
|
33
|
+
const HANDSET_ID: string = "8f2c1f0e-0000-4000-8000-0000000000cc";
|
|
34
|
+
const SITE_A: ObjectID = new ObjectID("5a5a5a5a-0000-4000-8000-00000000000a");
|
|
35
|
+
const SITE_B: ObjectID = new ObjectID("5b5b5b5b-0000-4000-8000-00000000000b");
|
|
36
|
+
|
|
37
|
+
const REGISTER_IP: string = "10.0.0.5";
|
|
38
|
+
const REGISTER_MAC: string = "aa:bb:cc:dd:ee:01";
|
|
39
|
+
const HANDSET_IP: string = "10.0.0.6";
|
|
40
|
+
const HANDSET_MAC: string = "aa:bb:cc:dd:ee:02";
|
|
41
|
+
|
|
42
|
+
/*
|
|
43
|
+
* Must track HOSTNAME_LOOKUP_CHUNK_SIZE in NetworkDeviceMacLearningUtil.
|
|
44
|
+
* Written out rather than imported (it is module-private) so that changing
|
|
45
|
+
* the util's constant fails HERE.
|
|
46
|
+
*/
|
|
47
|
+
const CHUNK_SIZE: number = 500;
|
|
48
|
+
|
|
49
|
+
let findBySpy: jest.SpyInstance;
|
|
50
|
+
let hookFreeUpdateSpy: jest.SpyInstance;
|
|
51
|
+
|
|
52
|
+
function deviceRow(
|
|
53
|
+
id: string,
|
|
54
|
+
hostname: string,
|
|
55
|
+
overrides?: {
|
|
56
|
+
siteId?: ObjectID;
|
|
57
|
+
macAddress?: string;
|
|
58
|
+
isMacAddressLearned?: boolean;
|
|
59
|
+
},
|
|
60
|
+
): NetworkDevice {
|
|
61
|
+
const row: NetworkDevice = new NetworkDevice();
|
|
62
|
+
row.id = new ObjectID(id);
|
|
63
|
+
row.hostname = hostname;
|
|
64
|
+
if (overrides?.siteId) {
|
|
65
|
+
row.siteId = overrides.siteId;
|
|
66
|
+
}
|
|
67
|
+
if (overrides?.macAddress !== undefined) {
|
|
68
|
+
row.macAddress = overrides.macAddress;
|
|
69
|
+
}
|
|
70
|
+
if (overrides?.isMacAddressLearned !== undefined) {
|
|
71
|
+
row.isMacAddressLearned = overrides.isMacAddressLearned;
|
|
72
|
+
}
|
|
73
|
+
return row;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function binding(ipAddress: string, macAddress: string): MacLearningIpBinding {
|
|
77
|
+
return { ipAddress, macAddress };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/*
|
|
81
|
+
* `count` distinct, well-formed addresses. Kept under 40 x 256 so every
|
|
82
|
+
* generated address is unique — the chunk-arithmetic case counts on it.
|
|
83
|
+
*/
|
|
84
|
+
function addresses(count: number): Array<string> {
|
|
85
|
+
const list: Array<string> = [];
|
|
86
|
+
|
|
87
|
+
for (let index: number = 0; index < count; index++) {
|
|
88
|
+
list.push(`10.99.${Math.floor(index / 256)}.${index % 256}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return list;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findByArgs(callIndex: number): JSONObject {
|
|
95
|
+
return findBySpy.mock.calls[callIndex]![0] as JSONObject;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function queryOf(callIndex: number): JSONObject {
|
|
99
|
+
return findByArgs(callIndex)["query"] as JSONObject;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/*
|
|
103
|
+
* The addresses one read actually asked about. QueryHelper.any renders the
|
|
104
|
+
* chunk into a TypeORM Raw operator — `(alias IN (:...rid))` with a RANDOM
|
|
105
|
+
* parameter name — so the list is recovered from the operator's parameters
|
|
106
|
+
* rather than from a key a test could hard-code.
|
|
107
|
+
*/
|
|
108
|
+
function hostnamesIn(query: JSONObject): Array<string> {
|
|
109
|
+
const operator: FindOperator<string> = query[
|
|
110
|
+
"hostname"
|
|
111
|
+
] as unknown as FindOperator<string>;
|
|
112
|
+
|
|
113
|
+
const parameters: Record<string, unknown> =
|
|
114
|
+
(operator.objectLiteralParameters || {}) as Record<string, unknown>;
|
|
115
|
+
|
|
116
|
+
const values: Array<unknown> =
|
|
117
|
+
(Object.values(parameters)[0] as Array<unknown>) || [];
|
|
118
|
+
|
|
119
|
+
return values.map((value: unknown): string => {
|
|
120
|
+
return String(value);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function askedAboutInCall(callIndex: number): Array<string> {
|
|
125
|
+
return hostnamesIn(queryOf(callIndex));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function chunkSizes(): Array<number> {
|
|
129
|
+
const sizes: Array<number> = [];
|
|
130
|
+
|
|
131
|
+
for (let index: number = 0; index < findBySpy.mock.calls.length; index++) {
|
|
132
|
+
sizes.push(askedAboutInCall(index).length);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return sizes;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/*
|
|
139
|
+
* findBy answers each chunk with the seeded rows whose hostname that chunk
|
|
140
|
+
* asked about — the way a real read would — so a candidate can only be
|
|
141
|
+
* planned if some chunk actually asked about its address.
|
|
142
|
+
*/
|
|
143
|
+
function mockServices(rows: Array<NetworkDevice> = []): void {
|
|
144
|
+
findBySpy = jest
|
|
145
|
+
.spyOn(NetworkDeviceService, "findBy")
|
|
146
|
+
.mockImplementation(
|
|
147
|
+
async (findBy: unknown): Promise<Array<NetworkDevice>> => {
|
|
148
|
+
const asked: Set<string> = new Set(
|
|
149
|
+
hostnamesIn((findBy as JSONObject)["query"] as JSONObject),
|
|
150
|
+
);
|
|
151
|
+
return rows.filter((row: NetworkDevice) => {
|
|
152
|
+
return asked.has(row.hostname || "");
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
);
|
|
156
|
+
hookFreeUpdateSpy = jest
|
|
157
|
+
.spyOn(NetworkDeviceService, "updateColumnsByIdWithoutHooks")
|
|
158
|
+
.mockResolvedValue(undefined);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function learn(
|
|
162
|
+
ipBindings: Array<MacLearningIpBinding>,
|
|
163
|
+
options?: { observingSiteId?: ObjectID | undefined },
|
|
164
|
+
): Promise<MacLearningResult> {
|
|
165
|
+
return NetworkDeviceMacLearningUtil.learnFromArpBindings({
|
|
166
|
+
projectId: new ObjectID(PROJECT_ID),
|
|
167
|
+
observingDeviceId: new ObjectID(ROUTER_ID),
|
|
168
|
+
observingSiteId: options?.observingSiteId,
|
|
169
|
+
ipBindings: ipBindings,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function writtenDeviceIds(): Array<string> {
|
|
174
|
+
return hookFreeUpdateSpy.mock.calls.map((call: Array<unknown>) => {
|
|
175
|
+
return ((call[0] as JSONObject)["id"] as ObjectID).toString();
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
afterEach(() => {
|
|
180
|
+
jest.restoreAllMocks();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe("NetworkDeviceMacLearningUtil.learnFromArpBindings — the candidate read", () => {
|
|
184
|
+
test("asks for the project's live devices at the bound addresses, and nothing narrower", async () => {
|
|
185
|
+
mockServices();
|
|
186
|
+
|
|
187
|
+
await learn([
|
|
188
|
+
binding(REGISTER_IP, REGISTER_MAC),
|
|
189
|
+
binding(HANDSET_IP, HANDSET_MAC),
|
|
190
|
+
]);
|
|
191
|
+
|
|
192
|
+
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
193
|
+
|
|
194
|
+
const query: JSONObject = queryOf(0);
|
|
195
|
+
|
|
196
|
+
expect((query["projectId"] as ObjectID).toString()).toBe(PROJECT_ID);
|
|
197
|
+
expect(query["isArchived"]).toBe(false);
|
|
198
|
+
expect(askedAboutInCall(0)).toEqual([REGISTER_IP, HANDSET_IP]);
|
|
199
|
+
/*
|
|
200
|
+
* Deliberately NOT filtered by site or by `macAddress: null`: the
|
|
201
|
+
* planner refuses on what a narrower query would have hidden.
|
|
202
|
+
*/
|
|
203
|
+
expect(Object.keys(query).sort()).toEqual([
|
|
204
|
+
"hostname",
|
|
205
|
+
"isArchived",
|
|
206
|
+
"projectId",
|
|
207
|
+
]);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("selects exactly the columns the planner keys on, as root", async () => {
|
|
211
|
+
mockServices();
|
|
212
|
+
|
|
213
|
+
await learn([binding(REGISTER_IP, REGISTER_MAC)]);
|
|
214
|
+
|
|
215
|
+
const args: JSONObject = findByArgs(0);
|
|
216
|
+
|
|
217
|
+
expect(args["select"]).toEqual({
|
|
218
|
+
_id: true,
|
|
219
|
+
hostname: true,
|
|
220
|
+
siteId: true,
|
|
221
|
+
macAddress: true,
|
|
222
|
+
isMacAddressLearned: true,
|
|
223
|
+
});
|
|
224
|
+
expect(args["props"]).toEqual({ isRoot: true });
|
|
225
|
+
expect(args["limit"]).toBe(LIMIT_MAX);
|
|
226
|
+
expect(args["skip"]).toBe(0);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("one address bound to two MACs is asked about once", async () => {
|
|
230
|
+
mockServices();
|
|
231
|
+
|
|
232
|
+
await learn([
|
|
233
|
+
binding(REGISTER_IP, REGISTER_MAC),
|
|
234
|
+
binding(REGISTER_IP, "aa:bb:cc:dd:ee:99"),
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
expect(askedAboutInCall(0)).toEqual([REGISTER_IP]);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("bound addresses are trimmed, and blank ones dropped, before the read", async () => {
|
|
241
|
+
mockServices();
|
|
242
|
+
|
|
243
|
+
await learn([
|
|
244
|
+
binding(` ${REGISTER_IP} `, REGISTER_MAC),
|
|
245
|
+
binding(" ", HANDSET_MAC),
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
expect(askedAboutInCall(0)).toEqual([REGISTER_IP]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("empty bindings issue no read and no write", async () => {
|
|
252
|
+
mockServices();
|
|
253
|
+
|
|
254
|
+
const result: MacLearningResult = await learn([]);
|
|
255
|
+
|
|
256
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
257
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
258
|
+
expect(result).toEqual({ candidateDeviceCount: 0, plannedCount: 0 });
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("bindings with only blank addresses issue no read either", async () => {
|
|
262
|
+
mockServices();
|
|
263
|
+
|
|
264
|
+
const result: MacLearningResult = await learn([binding("", REGISTER_MAC)]);
|
|
265
|
+
|
|
266
|
+
expect(findBySpy).not.toHaveBeenCalled();
|
|
267
|
+
expect(result).toEqual({ candidateDeviceCount: 0, plannedCount: 0 });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("1,001 distinct addresses are read in chunks of 500, and candidates from every chunk are planned", async () => {
|
|
271
|
+
const bound: Array<string> = addresses(CHUNK_SIZE * 2 + 1);
|
|
272
|
+
const firstInFirstChunk: string = bound[0]!;
|
|
273
|
+
const onlyOneInLastChunk: string = bound[bound.length - 1]!;
|
|
274
|
+
|
|
275
|
+
mockServices([
|
|
276
|
+
deviceRow(REGISTER_ID, firstInFirstChunk, { siteId: SITE_A }),
|
|
277
|
+
deviceRow(HANDSET_ID, onlyOneInLastChunk, { siteId: SITE_A }),
|
|
278
|
+
]);
|
|
279
|
+
|
|
280
|
+
const result: MacLearningResult = await learn(
|
|
281
|
+
bound.map((ip: string, index: number) => {
|
|
282
|
+
return binding(
|
|
283
|
+
ip,
|
|
284
|
+
`aa:bb:${Math.floor(index / 256)
|
|
285
|
+
.toString(16)
|
|
286
|
+
.padStart(
|
|
287
|
+
2,
|
|
288
|
+
"0",
|
|
289
|
+
)}:${(index % 256).toString(16).padStart(2, "0")}:00:01`,
|
|
290
|
+
);
|
|
291
|
+
}),
|
|
292
|
+
{ observingSiteId: SITE_A },
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(findBySpy).toHaveBeenCalledTimes(3);
|
|
296
|
+
expect(chunkSizes()).toEqual([CHUNK_SIZE, CHUNK_SIZE, 1]);
|
|
297
|
+
|
|
298
|
+
// Every address, once, across the three reads.
|
|
299
|
+
const asked: Array<string> = [];
|
|
300
|
+
for (let index: number = 0; index < findBySpy.mock.calls.length; index++) {
|
|
301
|
+
asked.push(...askedAboutInCall(index));
|
|
302
|
+
}
|
|
303
|
+
expect(asked).toEqual(bound);
|
|
304
|
+
|
|
305
|
+
// A device answered by the first chunk and one by the last both land.
|
|
306
|
+
expect(result).toEqual({ candidateDeviceCount: 2, plannedCount: 2 });
|
|
307
|
+
expect(writtenDeviceIds().sort()).toEqual([REGISTER_ID, HANDSET_ID].sort());
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("a returned row with no id is not a candidate", async () => {
|
|
311
|
+
const nameless: NetworkDevice = new NetworkDevice();
|
|
312
|
+
nameless.hostname = REGISTER_IP;
|
|
313
|
+
nameless.siteId = SITE_A;
|
|
314
|
+
mockServices([nameless]);
|
|
315
|
+
|
|
316
|
+
const result: MacLearningResult = await learn(
|
|
317
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
318
|
+
{ observingSiteId: SITE_A },
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
expect(result).toEqual({ candidateDeviceCount: 0, plannedCount: 0 });
|
|
322
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe("NetworkDeviceMacLearningUtil.learnFromArpBindings — the write", () => {
|
|
327
|
+
test("each planned device gets exactly one hook-free compare-and-set write", async () => {
|
|
328
|
+
mockServices([
|
|
329
|
+
deviceRow(REGISTER_ID, REGISTER_IP, { siteId: SITE_A }),
|
|
330
|
+
deviceRow(HANDSET_ID, HANDSET_IP, { siteId: SITE_A }),
|
|
331
|
+
]);
|
|
332
|
+
|
|
333
|
+
const result: MacLearningResult = await learn(
|
|
334
|
+
[
|
|
335
|
+
binding(REGISTER_IP, "AA-BB-CC-DD-EE-01"),
|
|
336
|
+
binding(HANDSET_IP, HANDSET_MAC),
|
|
337
|
+
],
|
|
338
|
+
{ observingSiteId: SITE_A },
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
expect(hookFreeUpdateSpy).toHaveBeenCalledTimes(2);
|
|
342
|
+
|
|
343
|
+
const byId: Map<string, JSONObject> = new Map(
|
|
344
|
+
hookFreeUpdateSpy.mock.calls.map((call: Array<unknown>) => {
|
|
345
|
+
const input: JSONObject = call[0] as JSONObject;
|
|
346
|
+
return [(input["id"] as ObjectID).toString(), input];
|
|
347
|
+
}),
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
expect(Array.from(byId.keys()).sort()).toEqual(
|
|
351
|
+
[REGISTER_ID, HANDSET_ID].sort(),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
const registerWrite: JSONObject = byId.get(REGISTER_ID)!;
|
|
355
|
+
// Normalized on the way in: the row never sees the ARP table's spelling.
|
|
356
|
+
expect(registerWrite["data"]).toEqual({
|
|
357
|
+
macAddress: REGISTER_MAC,
|
|
358
|
+
isMacAddressLearned: true,
|
|
359
|
+
});
|
|
360
|
+
/*
|
|
361
|
+
* Fill only while still empty, and never a row deleted between the
|
|
362
|
+
* read and this write. This guard is what makes the whole thing safe to
|
|
363
|
+
* run from every router's walk without a lock.
|
|
364
|
+
*/
|
|
365
|
+
expect(registerWrite["expectedData"]).toEqual({
|
|
366
|
+
macAddress: null,
|
|
367
|
+
deletedAt: null,
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
const handsetWrite: JSONObject = byId.get(HANDSET_ID)!;
|
|
371
|
+
expect(handsetWrite["data"]).toEqual({
|
|
372
|
+
macAddress: HANDSET_MAC,
|
|
373
|
+
isMacAddressLearned: true,
|
|
374
|
+
});
|
|
375
|
+
expect(handsetWrite["expectedData"]).toEqual({
|
|
376
|
+
macAddress: null,
|
|
377
|
+
deletedAt: null,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
expect(result).toEqual({ candidateDeviceCount: 2, plannedCount: 2 });
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("the write goes through updateColumnsByIdWithoutHooks and never the hooked update", async () => {
|
|
384
|
+
const hookedUpdateSpy: jest.SpyInstance = jest
|
|
385
|
+
.spyOn(NetworkDeviceService, "updateOneById")
|
|
386
|
+
.mockResolvedValue(1);
|
|
387
|
+
mockServices([deviceRow(REGISTER_ID, REGISTER_IP, { siteId: SITE_A })]);
|
|
388
|
+
|
|
389
|
+
await learn([binding(REGISTER_IP, REGISTER_MAC)], {
|
|
390
|
+
observingSiteId: SITE_A,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(hookFreeUpdateSpy).toHaveBeenCalledTimes(1);
|
|
394
|
+
expect(hookedUpdateSpy).not.toHaveBeenCalled();
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test("a device already carrying a MAC is a candidate but is never written", async () => {
|
|
398
|
+
mockServices([
|
|
399
|
+
deviceRow(REGISTER_ID, REGISTER_IP, {
|
|
400
|
+
siteId: SITE_A,
|
|
401
|
+
macAddress: "00:11:22:33:44:55",
|
|
402
|
+
}),
|
|
403
|
+
]);
|
|
404
|
+
|
|
405
|
+
const result: MacLearningResult = await learn(
|
|
406
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
407
|
+
{ observingSiteId: SITE_A },
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
411
|
+
expect(result).toEqual({ candidateDeviceCount: 1, plannedCount: 0 });
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("a device in another site is a candidate but is never written", async () => {
|
|
415
|
+
mockServices([deviceRow(REGISTER_ID, REGISTER_IP, { siteId: SITE_B })]);
|
|
416
|
+
|
|
417
|
+
const result: MacLearningResult = await learn(
|
|
418
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
419
|
+
{ observingSiteId: SITE_A },
|
|
420
|
+
);
|
|
421
|
+
|
|
422
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
423
|
+
expect(result).toEqual({ candidateDeviceCount: 1, plannedCount: 0 });
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("the observing site is compared as a string, so an equal ObjectID matches", async () => {
|
|
427
|
+
// A fresh ObjectID with the same value: equal by string, not by reference.
|
|
428
|
+
mockServices([
|
|
429
|
+
deviceRow(REGISTER_ID, REGISTER_IP, {
|
|
430
|
+
siteId: new ObjectID(SITE_A.toString()),
|
|
431
|
+
}),
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
const result: MacLearningResult = await learn(
|
|
435
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
436
|
+
{ observingSiteId: new ObjectID(SITE_A.toString()) },
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
expect(result).toEqual({ candidateDeviceCount: 1, plannedCount: 1 });
|
|
440
|
+
expect(writtenDeviceIds()).toEqual([REGISTER_ID]);
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
test("a router with no site stamps only devices with no site", async () => {
|
|
444
|
+
mockServices([
|
|
445
|
+
deviceRow(REGISTER_ID, REGISTER_IP),
|
|
446
|
+
deviceRow(HANDSET_ID, HANDSET_IP, { siteId: SITE_A }),
|
|
447
|
+
]);
|
|
448
|
+
|
|
449
|
+
const result: MacLearningResult = await learn(
|
|
450
|
+
[binding(REGISTER_IP, REGISTER_MAC), binding(HANDSET_IP, HANDSET_MAC)],
|
|
451
|
+
{ observingSiteId: undefined },
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
expect(writtenDeviceIds()).toEqual([REGISTER_ID]);
|
|
455
|
+
expect(result).toEqual({ candidateDeviceCount: 2, plannedCount: 1 });
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("two devices at one address: neither is written, but both are counted as candidates", async () => {
|
|
459
|
+
mockServices([
|
|
460
|
+
deviceRow(REGISTER_ID, REGISTER_IP, { siteId: SITE_A }),
|
|
461
|
+
deviceRow(HANDSET_ID, REGISTER_IP, { siteId: SITE_A }),
|
|
462
|
+
]);
|
|
463
|
+
|
|
464
|
+
const result: MacLearningResult = await learn(
|
|
465
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
466
|
+
{ observingSiteId: SITE_A },
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
470
|
+
expect(result).toEqual({ candidateDeviceCount: 2, plannedCount: 0 });
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("nothing is written when no device is registered at any bound address", async () => {
|
|
474
|
+
mockServices([deviceRow(REGISTER_ID, "10.0.0.250", { siteId: SITE_A })]);
|
|
475
|
+
|
|
476
|
+
const result: MacLearningResult = await learn(
|
|
477
|
+
[binding(REGISTER_IP, REGISTER_MAC)],
|
|
478
|
+
{ observingSiteId: SITE_A },
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
expect(findBySpy).toHaveBeenCalledTimes(1);
|
|
482
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
483
|
+
expect(result).toEqual({ candidateDeviceCount: 0, plannedCount: 0 });
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("a write that throws surfaces to the caller, after the writes before it landed", async () => {
|
|
487
|
+
/*
|
|
488
|
+
* The util does not swallow: the walk pipeline wraps this call in its
|
|
489
|
+
* own try/catch (pinned in NetworkInventoryUtil.test.ts), and a silent
|
|
490
|
+
* catch here would hide a broken write path from that log line.
|
|
491
|
+
*/
|
|
492
|
+
mockServices([
|
|
493
|
+
deviceRow(REGISTER_ID, REGISTER_IP, { siteId: SITE_A }),
|
|
494
|
+
deviceRow(HANDSET_ID, HANDSET_IP, { siteId: SITE_A }),
|
|
495
|
+
]);
|
|
496
|
+
hookFreeUpdateSpy
|
|
497
|
+
.mockResolvedValueOnce(undefined)
|
|
498
|
+
.mockRejectedValueOnce(new Error("connection reset"));
|
|
499
|
+
|
|
500
|
+
await expect(
|
|
501
|
+
learn(
|
|
502
|
+
[binding(REGISTER_IP, REGISTER_MAC), binding(HANDSET_IP, HANDSET_MAC)],
|
|
503
|
+
{ observingSiteId: SITE_A },
|
|
504
|
+
),
|
|
505
|
+
).rejects.toThrow("connection reset");
|
|
506
|
+
|
|
507
|
+
expect(hookFreeUpdateSpy).toHaveBeenCalledTimes(2);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
/*
|
|
512
|
+
* The pass may correct a MAC it learned itself - the ARP table it learned
|
|
513
|
+
* from is exactly the evidence that goes stale - and only that. The write
|
|
514
|
+
* compares-and-sets on the value the plan saw, so a value that changed
|
|
515
|
+
* under the plan (an operator typing one in) is left alone by the row.
|
|
516
|
+
*/
|
|
517
|
+
describe("NetworkDeviceMacLearningUtil.learnFromArpBindings — correcting a learned MAC", () => {
|
|
518
|
+
const STALE_MAC: string = "de:ad:be:ef:00:01";
|
|
519
|
+
|
|
520
|
+
test("a learned MAC the table now contradicts is rewritten, compare-and-set on the old value", async () => {
|
|
521
|
+
mockServices([
|
|
522
|
+
deviceRow(REGISTER_ID, REGISTER_IP, {
|
|
523
|
+
macAddress: STALE_MAC,
|
|
524
|
+
isMacAddressLearned: true,
|
|
525
|
+
}),
|
|
526
|
+
]);
|
|
527
|
+
|
|
528
|
+
const result: MacLearningResult = await learn([
|
|
529
|
+
binding(REGISTER_IP, REGISTER_MAC),
|
|
530
|
+
]);
|
|
531
|
+
|
|
532
|
+
expect(hookFreeUpdateSpy).toHaveBeenCalledTimes(1);
|
|
533
|
+
const write: JSONObject = hookFreeUpdateSpy.mock.calls[0]![0] as JSONObject;
|
|
534
|
+
expect(write["data"]).toEqual({
|
|
535
|
+
macAddress: REGISTER_MAC,
|
|
536
|
+
isMacAddressLearned: true,
|
|
537
|
+
});
|
|
538
|
+
expect(write["expectedData"]).toEqual({
|
|
539
|
+
macAddress: STALE_MAC,
|
|
540
|
+
deletedAt: null,
|
|
541
|
+
});
|
|
542
|
+
expect(result).toEqual({ candidateDeviceCount: 1, plannedCount: 1 });
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
test("a typed MAC is never rewritten, however much the table disagrees", async () => {
|
|
546
|
+
mockServices([
|
|
547
|
+
deviceRow(REGISTER_ID, REGISTER_IP, {
|
|
548
|
+
macAddress: STALE_MAC,
|
|
549
|
+
isMacAddressLearned: false,
|
|
550
|
+
}),
|
|
551
|
+
]);
|
|
552
|
+
|
|
553
|
+
const result: MacLearningResult = await learn([
|
|
554
|
+
binding(REGISTER_IP, REGISTER_MAC),
|
|
555
|
+
]);
|
|
556
|
+
|
|
557
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
558
|
+
expect(result).toEqual({ candidateDeviceCount: 1, plannedCount: 0 });
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
test("a learned MAC the table still agrees with is left alone", async () => {
|
|
562
|
+
mockServices([
|
|
563
|
+
deviceRow(REGISTER_ID, REGISTER_IP, {
|
|
564
|
+
macAddress: REGISTER_MAC,
|
|
565
|
+
isMacAddressLearned: true,
|
|
566
|
+
}),
|
|
567
|
+
]);
|
|
568
|
+
|
|
569
|
+
await learn([binding(REGISTER_IP, REGISTER_MAC)]);
|
|
570
|
+
|
|
571
|
+
expect(hookFreeUpdateSpy).not.toHaveBeenCalled();
|
|
572
|
+
});
|
|
573
|
+
});
|