@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
|
@@ -41,6 +41,7 @@ import MetricQueryConfigData from "../../../Types/Metrics/MetricQueryConfigData"
|
|
|
41
41
|
import MetricFormulaConfigData from "../../../Types/Metrics/MetricFormulaConfigData";
|
|
42
42
|
import MetricsViewConfig from "../../../Types/Metrics/MetricsViewConfig";
|
|
43
43
|
import MetricUnitUtil from "../../../Utils/MetricUnitUtil";
|
|
44
|
+
import MetricValueFormatter from "../../../Utils/Monitor/MetricValueFormatter";
|
|
44
45
|
|
|
45
46
|
export default class MonitorCriteriaObservationBuilder {
|
|
46
47
|
public static describeFilterObservation(input: {
|
|
@@ -1220,18 +1221,33 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1220
1221
|
* that unit alongside the numbers so the message reads "latest 0.06 sec"
|
|
1221
1222
|
* instead of the unitless "latest 0.06".
|
|
1222
1223
|
*/
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1224
|
+
const resolvedUnits: {
|
|
1225
|
+
displayUnit: string | undefined;
|
|
1226
|
+
metricName: string | undefined;
|
|
1227
|
+
} = MonitorCriteriaObservationBuilder.resolveMetricUnits({
|
|
1228
|
+
criteriaFilter: input.criteriaFilter,
|
|
1229
|
+
dataToProcess: input.dataToProcess,
|
|
1230
|
+
monitorStep: input.monitorStep,
|
|
1231
|
+
alias: metricValues.alias,
|
|
1232
|
+
});
|
|
1230
1233
|
|
|
1234
|
+
/*
|
|
1235
|
+
* The same renderer the fired-alert email uses. Before this, a breach
|
|
1236
|
+
* that CompareCriteria wrote into the email as "1.07 GB" appeared in
|
|
1237
|
+
* the monitor's own evaluation log as "1073741824.00 By" — one sample,
|
|
1238
|
+
* two descriptions, on two screens a reader compares.
|
|
1239
|
+
*/
|
|
1231
1240
|
const summary: string | null =
|
|
1232
1241
|
MonitorCriteriaMessageFormatter.summarizeNumericSeries(
|
|
1233
1242
|
displayValues,
|
|
1234
|
-
displayUnit,
|
|
1243
|
+
resolvedUnits.displayUnit,
|
|
1244
|
+
(value: number): string => {
|
|
1245
|
+
return MetricValueFormatter.format({
|
|
1246
|
+
value: value,
|
|
1247
|
+
unit: resolvedUnits.displayUnit,
|
|
1248
|
+
metricName: resolvedUnits.metricName,
|
|
1249
|
+
});
|
|
1250
|
+
},
|
|
1235
1251
|
);
|
|
1236
1252
|
|
|
1237
1253
|
if (!summary) {
|
|
@@ -1304,6 +1320,18 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1304
1320
|
sampleUnit: string | undefined;
|
|
1305
1321
|
thresholdUnit: string | undefined;
|
|
1306
1322
|
displayUnit: string | undefined;
|
|
1323
|
+
/**
|
|
1324
|
+
* The metric NAME, for the two decisions MetricValueFormatter cannot
|
|
1325
|
+
* make from a unit alone: whether a "1"-unit value is a fraction to
|
|
1326
|
+
* render as a percent, and whether a "seconds"-unit value is really an
|
|
1327
|
+
* epoch that must skip the duration ladder.
|
|
1328
|
+
*
|
|
1329
|
+
* Undefined for a formula. A formula's "name" is its expression, and an
|
|
1330
|
+
* expression ending in `_ratio` would trip the fraction heuristic into
|
|
1331
|
+
* reporting every value at 100x — the same guard
|
|
1332
|
+
* MonitorCriteriaEvaluator.metricNameForUnitHeuristics applies.
|
|
1333
|
+
*/
|
|
1334
|
+
metricName: string | undefined;
|
|
1307
1335
|
} {
|
|
1308
1336
|
const thresholdUnit: string | undefined =
|
|
1309
1337
|
input.criteriaFilter.metricMonitorOptions?.thresholdUnit || undefined;
|
|
@@ -1317,6 +1345,7 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1317
1345
|
sampleUnit: undefined,
|
|
1318
1346
|
thresholdUnit,
|
|
1319
1347
|
displayUnit: thresholdUnit,
|
|
1348
|
+
metricName: undefined,
|
|
1320
1349
|
};
|
|
1321
1350
|
}
|
|
1322
1351
|
|
|
@@ -1355,35 +1384,42 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1355
1384
|
nativeUnitFromMap ||
|
|
1356
1385
|
undefined;
|
|
1357
1386
|
|
|
1387
|
+
const displayUnit: string | undefined = thresholdUnit || sampleUnit;
|
|
1388
|
+
|
|
1358
1389
|
return {
|
|
1359
1390
|
sampleUnit,
|
|
1360
1391
|
thresholdUnit,
|
|
1361
1392
|
displayUnit: MonitorCriteriaObservationBuilder.normalizeDisplayUnit(
|
|
1362
|
-
|
|
1393
|
+
displayUnit,
|
|
1394
|
+
rawMetricName,
|
|
1363
1395
|
),
|
|
1396
|
+
metricName: rawMetricName,
|
|
1364
1397
|
};
|
|
1365
1398
|
}
|
|
1366
1399
|
|
|
1367
1400
|
/*
|
|
1368
|
-
* Suppress units that
|
|
1369
|
-
*
|
|
1370
|
-
*
|
|
1371
|
-
*
|
|
1372
|
-
*
|
|
1373
|
-
*
|
|
1401
|
+
* Suppress units that name no dimension, so they never reach a reader
|
|
1402
|
+
* next to a number.
|
|
1403
|
+
*
|
|
1404
|
+
* This used to be a hand-rolled rule that dropped only the literal "1",
|
|
1405
|
+
* and it drifted from the one CompareCriteria applies to the very same
|
|
1406
|
+
* sample: a ratio metric's evaluation log read a bare "latest 0.06"
|
|
1407
|
+
* while the alert email for the identical value said "6.00%". It also
|
|
1408
|
+
* let UCUM's "{restarts}" and the platform catalogs' "count" / "ratio"
|
|
1409
|
+
* through, none of which mean anything to a reader.
|
|
1410
|
+
*
|
|
1411
|
+
* MetricValueFormatter owns that rule now. Note it is name-AWARE where
|
|
1412
|
+
* the old one was not: "1" on a `.utilization` metric is a real
|
|
1413
|
+
* dimension (a fraction to be shown as a percent), so it survives here
|
|
1414
|
+
* and the formatter turns it into "%".
|
|
1374
1415
|
*/
|
|
1375
1416
|
private static normalizeDisplayUnit(
|
|
1376
1417
|
unit: string | undefined,
|
|
1418
|
+
metricName?: string | undefined,
|
|
1377
1419
|
): string | undefined {
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
if (unit.trim() === "1") {
|
|
1383
|
-
return undefined;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
return unit;
|
|
1420
|
+
return MetricValueFormatter.hasDisplayableUnit(unit, metricName)
|
|
1421
|
+
? unit
|
|
1422
|
+
: undefined;
|
|
1387
1423
|
}
|
|
1388
1424
|
|
|
1389
1425
|
/*
|
|
@@ -1399,6 +1435,24 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1399
1435
|
dataToProcess: DataToProcess;
|
|
1400
1436
|
monitorStep: MonitorStep;
|
|
1401
1437
|
}): string | undefined {
|
|
1438
|
+
return MonitorCriteriaObservationBuilder.getMetricValueDisplayContext(input)
|
|
1439
|
+
.unit;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
/**
|
|
1443
|
+
* The unit AND the metric name a criteria's values are displayed in.
|
|
1444
|
+
*
|
|
1445
|
+
* The expectation clause needs both, not just the unit: it renders the
|
|
1446
|
+
* threshold through the same MetricValueFormatter as the observation, and
|
|
1447
|
+
* the formatter's fraction and epoch-timestamp rules are keyed on the
|
|
1448
|
+
* name. Without it, "recorded latest 6.00%" would be followed by
|
|
1449
|
+
* "(expected to be greater than 0.9)".
|
|
1450
|
+
*/
|
|
1451
|
+
public static getMetricValueDisplayContext(input: {
|
|
1452
|
+
criteriaFilter: CriteriaFilter;
|
|
1453
|
+
dataToProcess: DataToProcess;
|
|
1454
|
+
monitorStep: MonitorStep;
|
|
1455
|
+
}): { unit: string | undefined; metricName: string | undefined } {
|
|
1402
1456
|
/*
|
|
1403
1457
|
* Database Health thresholds are typed in the metric's own unit, which
|
|
1404
1458
|
* only the catalog knows - without this the expectation clause reads
|
|
@@ -1412,13 +1466,17 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1412
1466
|
? getDatabaseMetricByMetricType(metricType)
|
|
1413
1467
|
: null;
|
|
1414
1468
|
|
|
1415
|
-
return
|
|
1416
|
-
|
|
1417
|
-
|
|
1469
|
+
return {
|
|
1470
|
+
unit: MonitorCriteriaObservationBuilder.normalizeDisplayUnit(
|
|
1471
|
+
definition?.unit,
|
|
1472
|
+
),
|
|
1473
|
+
// Catalog units are explicit; no name-based heuristic is wanted.
|
|
1474
|
+
metricName: undefined,
|
|
1475
|
+
};
|
|
1418
1476
|
}
|
|
1419
1477
|
|
|
1420
1478
|
if (input.criteriaFilter.checkOn !== CheckOn.MetricValue) {
|
|
1421
|
-
return undefined;
|
|
1479
|
+
return { unit: undefined, metricName: undefined };
|
|
1422
1480
|
}
|
|
1423
1481
|
|
|
1424
1482
|
const metricValues: {
|
|
@@ -1430,12 +1488,17 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1430
1488
|
monitorStep: input.monitorStep,
|
|
1431
1489
|
});
|
|
1432
1490
|
|
|
1433
|
-
|
|
1491
|
+
const resolved: {
|
|
1492
|
+
displayUnit: string | undefined;
|
|
1493
|
+
metricName: string | undefined;
|
|
1494
|
+
} = MonitorCriteriaObservationBuilder.resolveMetricUnits({
|
|
1434
1495
|
criteriaFilter: input.criteriaFilter,
|
|
1435
1496
|
dataToProcess: input.dataToProcess,
|
|
1436
1497
|
monitorStep: input.monitorStep,
|
|
1437
1498
|
alias: metricValues?.alias ?? null,
|
|
1438
|
-
})
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
return { unit: resolved.displayUnit, metricName: resolved.metricName };
|
|
1439
1502
|
}
|
|
1440
1503
|
|
|
1441
1504
|
private static getSnmpResponse(input: {
|
|
@@ -1743,28 +1806,24 @@ export default class MonitorCriteriaObservationBuilder {
|
|
|
1743
1806
|
}
|
|
1744
1807
|
}
|
|
1745
1808
|
|
|
1809
|
+
/*
|
|
1810
|
+
* A Database Health metric carries an explicit catalog unit, and
|
|
1811
|
+
* DatabaseMonitorCriteria already hands that same unit to CompareCriteria
|
|
1812
|
+
* for the fired-alert email. Rendering it here through anything else made
|
|
1813
|
+
* one byte count read three ways in one product: the email said
|
|
1814
|
+
* "1.07 GB" (decimal, via MetricValueFormatter), this observation said
|
|
1815
|
+
* "1.00 GB" (formatBytes divides by 1024 but labels the rungs SI), and
|
|
1816
|
+
* the expectation clause beside it said "1000000000 bytes".
|
|
1817
|
+
*
|
|
1818
|
+
* formatBytes stays for the server-monitor memory and disk observations,
|
|
1819
|
+
* where the values come from BasicMetrics with no unit metadata at all
|
|
1820
|
+
* and 1024 is the right convention for RAM and volumes.
|
|
1821
|
+
*/
|
|
1746
1822
|
private static formatDatabaseMetricValue(
|
|
1747
1823
|
value: number,
|
|
1748
1824
|
unit: string,
|
|
1749
1825
|
): string {
|
|
1750
|
-
|
|
1751
|
-
return (
|
|
1752
|
-
MonitorCriteriaMessageFormatter.formatPercentage(value) ?? String(value)
|
|
1753
|
-
);
|
|
1754
|
-
}
|
|
1755
|
-
|
|
1756
|
-
if (unit === "bytes") {
|
|
1757
|
-
return (
|
|
1758
|
-
MonitorCriteriaMessageFormatter.formatBytes(value) ?? String(value)
|
|
1759
|
-
);
|
|
1760
|
-
}
|
|
1761
|
-
|
|
1762
|
-
const formatted: string =
|
|
1763
|
-
MonitorCriteriaMessageFormatter.formatNumber(value, {
|
|
1764
|
-
maximumFractionDigits: 2,
|
|
1765
|
-
}) ?? String(value);
|
|
1766
|
-
|
|
1767
|
-
return unit ? `${formatted} ${unit}` : formatted;
|
|
1826
|
+
return MetricValueFormatter.format({ value: value, unit: unit });
|
|
1768
1827
|
}
|
|
1769
1828
|
|
|
1770
1829
|
private static describeDatabaseCollectionErrorObservation(input: {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
2
|
+
import NetworkDeviceService from "../../Services/NetworkDeviceService";
|
|
3
|
+
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
4
|
+
import LIMIT_MAX from "../../../Types/Database/LimitMax";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
import DeviceMacLearningUtil, {
|
|
7
|
+
MacLearningCandidateDevice,
|
|
8
|
+
MacLearningIpBinding,
|
|
9
|
+
MacLearningPlanEntry,
|
|
10
|
+
} from "../../../Utils/Monitor/DeviceMacLearningUtil";
|
|
11
|
+
import logger from "../Logger";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Fills NetworkDevice.macAddress from a walked router's ARP table.
|
|
15
|
+
*
|
|
16
|
+
* Runs once per successful walk that carried ARP entries, after the
|
|
17
|
+
* endpoint inventory has been written. The decision of WHICH devices to
|
|
18
|
+
* stamp is DeviceMacLearningUtil.planMacLearning (pure, tested without a
|
|
19
|
+
* database); this only fetches the candidates and applies the plan.
|
|
20
|
+
*
|
|
21
|
+
* The write is hook-free and compare-and-set: a row is stamped only while
|
|
22
|
+
* its MAC column still holds what the plan saw (NULL for a first learn, the
|
|
23
|
+
* previously learned MAC for a correction), so an operator who types a MAC
|
|
24
|
+
* between the read and the write keeps theirs, and two walks racing on one
|
|
25
|
+
* row cannot both land. A stable site plans nothing - every device already
|
|
26
|
+
* holds what the table says - so it costs one indexed read per walk and no
|
|
27
|
+
* writes, which is what keeps this affordable at fleet scale.
|
|
28
|
+
*
|
|
29
|
+
* The write cannot tell the caller whether the compare-and-set matched a
|
|
30
|
+
* row, so the result counts what was PLANNED, not what landed.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
// Same chunking NetworkDeviceService.getDevicesByHostnames uses.
|
|
34
|
+
const HOSTNAME_LOOKUP_CHUNK_SIZE: number = 500;
|
|
35
|
+
|
|
36
|
+
export interface MacLearningResult {
|
|
37
|
+
candidateDeviceCount: number;
|
|
38
|
+
// Writes issued. A compare-and-set that matched nothing is not subtracted.
|
|
39
|
+
plannedCount: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export default class NetworkDeviceMacLearningUtil {
|
|
43
|
+
public static async learnFromArpBindings(data: {
|
|
44
|
+
projectId: ObjectID;
|
|
45
|
+
// The router (or L3 switch) whose ARP table this is.
|
|
46
|
+
observingDeviceId: ObjectID;
|
|
47
|
+
observingSiteId: ObjectID | undefined;
|
|
48
|
+
ipBindings: Array<MacLearningIpBinding>;
|
|
49
|
+
}): Promise<MacLearningResult> {
|
|
50
|
+
const addresses: Array<string> = Array.from(
|
|
51
|
+
new Set<string>(
|
|
52
|
+
data.ipBindings
|
|
53
|
+
.map((binding: MacLearningIpBinding) => {
|
|
54
|
+
return (binding.ipAddress || "").trim();
|
|
55
|
+
})
|
|
56
|
+
.filter((ip: string) => {
|
|
57
|
+
return ip.length > 0;
|
|
58
|
+
}),
|
|
59
|
+
),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
if (addresses.length === 0) {
|
|
63
|
+
return { candidateDeviceCount: 0, plannedCount: 0 };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Every live device in the project registered at one of the bound
|
|
68
|
+
* addresses. Not filtered by site or by an empty MAC column in SQL:
|
|
69
|
+
* the planner needs to SEE a second device at the same address, or a
|
|
70
|
+
* device that already has a MAC, to refuse correctly - a query that
|
|
71
|
+
* hid them would make "exactly one" true by accident.
|
|
72
|
+
*/
|
|
73
|
+
const candidates: Array<MacLearningCandidateDevice> = [];
|
|
74
|
+
|
|
75
|
+
for (
|
|
76
|
+
let offset: number = 0;
|
|
77
|
+
offset < addresses.length;
|
|
78
|
+
offset += HOSTNAME_LOOKUP_CHUNK_SIZE
|
|
79
|
+
) {
|
|
80
|
+
const chunk: Array<string> = addresses.slice(
|
|
81
|
+
offset,
|
|
82
|
+
offset + HOSTNAME_LOOKUP_CHUNK_SIZE,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const devices: Array<NetworkDevice> = await NetworkDeviceService.findBy({
|
|
86
|
+
query: {
|
|
87
|
+
projectId: data.projectId,
|
|
88
|
+
hostname: QueryHelper.any(chunk),
|
|
89
|
+
isArchived: false,
|
|
90
|
+
},
|
|
91
|
+
select: {
|
|
92
|
+
_id: true,
|
|
93
|
+
hostname: true,
|
|
94
|
+
siteId: true,
|
|
95
|
+
macAddress: true,
|
|
96
|
+
isMacAddressLearned: true,
|
|
97
|
+
},
|
|
98
|
+
limit: LIMIT_MAX,
|
|
99
|
+
skip: 0,
|
|
100
|
+
props: {
|
|
101
|
+
isRoot: true,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
for (const device of devices) {
|
|
106
|
+
if (!device.id) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
candidates.push({
|
|
110
|
+
id: device.id.toString(),
|
|
111
|
+
hostname: device.hostname,
|
|
112
|
+
siteId: device.siteId?.toString(),
|
|
113
|
+
macAddress: device.macAddress,
|
|
114
|
+
isMacAddressLearned: device.isMacAddressLearned,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const plan: Array<MacLearningPlanEntry> =
|
|
120
|
+
DeviceMacLearningUtil.planMacLearning({
|
|
121
|
+
ipBindings: data.ipBindings,
|
|
122
|
+
devices: candidates,
|
|
123
|
+
observingSiteId: data.observingSiteId?.toString(),
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
let plannedCount: number = 0;
|
|
127
|
+
|
|
128
|
+
for (const entry of plan) {
|
|
129
|
+
await NetworkDeviceService.updateColumnsByIdWithoutHooks({
|
|
130
|
+
id: new ObjectID(entry.deviceId),
|
|
131
|
+
data: {
|
|
132
|
+
macAddress: entry.macAddress,
|
|
133
|
+
isMacAddressLearned: true,
|
|
134
|
+
} as any,
|
|
135
|
+
/*
|
|
136
|
+
* Only while the column still holds what the plan saw - empty, or
|
|
137
|
+
* the learned value being corrected - and never a row that was
|
|
138
|
+
* deleted between the read and this write.
|
|
139
|
+
*/
|
|
140
|
+
expectedData: {
|
|
141
|
+
macAddress: entry.previousMacAddress,
|
|
142
|
+
deletedAt: null,
|
|
143
|
+
} as any,
|
|
144
|
+
});
|
|
145
|
+
plannedCount++;
|
|
146
|
+
|
|
147
|
+
logger.debug(
|
|
148
|
+
`Planned MAC ${entry.macAddress} for network device ${entry.deviceId} (${entry.ipAddress}) from the ARP table of device ${data.observingDeviceId.toString()}${
|
|
149
|
+
entry.previousMacAddress
|
|
150
|
+
? `, correcting the learned ${entry.previousMacAddress}`
|
|
151
|
+
: ""
|
|
152
|
+
}; skipped if the column changed meanwhile.`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { candidateDeviceCount: candidates.length, plannedCount };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import NetworkDevice from "../../../Models/DatabaseModels/NetworkDevice";
|
|
2
2
|
import NetworkDeviceService from "../../Services/NetworkDeviceService";
|
|
3
3
|
import NetworkEndpointService from "../../Services/NetworkEndpointService";
|
|
4
|
+
import NetworkDeviceMacLearningUtil from "./NetworkDeviceMacLearningUtil";
|
|
4
5
|
import NetworkInterfaceService, {
|
|
5
6
|
InterfaceWalkUpsertResult,
|
|
6
7
|
} from "../../Services/NetworkInterfaceService";
|
|
@@ -445,6 +446,32 @@ export default class NetworkInventoryUtil {
|
|
|
445
446
|
now: now,
|
|
446
447
|
});
|
|
447
448
|
}
|
|
449
|
+
|
|
450
|
+
/*
|
|
451
|
+
* --- Device MAC learning (ARP) ---
|
|
452
|
+
* The ARP table also names the MACs of managed devices registered
|
|
453
|
+
* at the addresses it binds - the ping-only registers and handsets
|
|
454
|
+
* that the switches' forwarding tables can put on a port, but only
|
|
455
|
+
* once something has said which MAC is theirs. Stamped on the
|
|
456
|
+
* device, once, while its MAC column is empty. Its own try/catch:
|
|
457
|
+
* the endpoint inventory above is already written, and a failure
|
|
458
|
+
* here must not read as one there.
|
|
459
|
+
*/
|
|
460
|
+
if (endpointResult.ipBindings.length > 0) {
|
|
461
|
+
try {
|
|
462
|
+
await NetworkDeviceMacLearningUtil.learnFromArpBindings({
|
|
463
|
+
projectId: data.projectId,
|
|
464
|
+
observingDeviceId: deviceId,
|
|
465
|
+
observingSiteId: ownedDevice.siteId,
|
|
466
|
+
ipBindings: endpointResult.ipBindings,
|
|
467
|
+
});
|
|
468
|
+
} catch (err) {
|
|
469
|
+
logger.error(
|
|
470
|
+
`Failed to learn device MAC addresses from the ARP table of network device ${deviceId.toString()}:`,
|
|
471
|
+
);
|
|
472
|
+
logger.error(err);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
448
475
|
}
|
|
449
476
|
} catch (err) {
|
|
450
477
|
// Inventory bookkeeping must never fail the walk pipeline.
|
|
@@ -217,7 +217,17 @@ export async function reconcileEntityRegistryThrottled(data: {
|
|
|
217
217
|
try {
|
|
218
218
|
const promoted: Array<ExtractedEntity> = data.entities.filter(
|
|
219
219
|
(entity: ExtractedEntity) => {
|
|
220
|
-
|
|
220
|
+
/*
|
|
221
|
+
* Two gates, and they answer different questions. The type gate asks
|
|
222
|
+
* "does this KIND of thing belong in the registry"; the per-entity
|
|
223
|
+
* flag asks "is this particular observation the resource's entity of
|
|
224
|
+
* that type, or a duplicate the producer sent alongside it". See
|
|
225
|
+
* `ExtractedEntity.membershipOnly`.
|
|
226
|
+
*/
|
|
227
|
+
return (
|
|
228
|
+
!entity.membershipOnly &&
|
|
229
|
+
REGISTRY_PROMOTED_TYPES.has(entity.entityType)
|
|
230
|
+
);
|
|
221
231
|
},
|
|
222
232
|
);
|
|
223
233
|
const retiredHosts: Array<RetiredEntityIdentity> = (
|
|
@@ -82,6 +82,20 @@ export interface ExtractedEntity {
|
|
|
82
82
|
* the registry attaches them via the existing project label system.
|
|
83
83
|
*/
|
|
84
84
|
labels?: Array<string>;
|
|
85
|
+
/**
|
|
86
|
+
* When true this entity contributes its key to `entityKeys` on signals but
|
|
87
|
+
* must never mint a registry row.
|
|
88
|
+
*
|
|
89
|
+
* One OTLP resource describes ONE object of any given type — it is a single
|
|
90
|
+
* pod, on a single node, in a single cluster — and the rest of the pipeline
|
|
91
|
+
* relies on that: `scalarEntityKeysFromEntities` keeps the first key per
|
|
92
|
+
* type, and the heuristic resolvers emit at most one entity per type by
|
|
93
|
+
* construction. A producer can nonetheless send two `entity_refs` of the
|
|
94
|
+
* same type on one resource, and promoting both put two rows in Inventory
|
|
95
|
+
* from a single batch. The extra refs stay queryable as membership keys and
|
|
96
|
+
* stop there.
|
|
97
|
+
*/
|
|
98
|
+
membershipOnly?: boolean;
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
/**
|
|
@@ -302,6 +316,12 @@ export default class InventoryItem {
|
|
|
302
316
|
}): Array<ExtractedEntity> {
|
|
303
317
|
const out: Array<ExtractedEntity> = [];
|
|
304
318
|
const seen: Set<string> = new Set<string>();
|
|
319
|
+
/*
|
|
320
|
+
* First usable ref of a type is the one that gets a registry row; any
|
|
321
|
+
* later ref of that same type is demoted to membership-only. See
|
|
322
|
+
* `ExtractedEntity.membershipOnly`.
|
|
323
|
+
*/
|
|
324
|
+
const promotedTypes: Set<EntityType> = new Set<EntityType>();
|
|
305
325
|
|
|
306
326
|
for (const ref of data.entityRefs) {
|
|
307
327
|
const refType: string =
|
|
@@ -381,6 +401,15 @@ export default class InventoryItem {
|
|
|
381
401
|
}
|
|
382
402
|
}
|
|
383
403
|
|
|
404
|
+
const alreadyPromoted: boolean = promotedTypes.has(entityType);
|
|
405
|
+
if (alreadyPromoted) {
|
|
406
|
+
logger.debug(
|
|
407
|
+
`OTLP resource declares more than one entity_ref of type "${refType}"; keeping the first as the entity and the rest as membership keys only.`,
|
|
408
|
+
);
|
|
409
|
+
} else {
|
|
410
|
+
promotedTypes.add(entityType);
|
|
411
|
+
}
|
|
412
|
+
|
|
384
413
|
out.push({
|
|
385
414
|
entityType,
|
|
386
415
|
entityKey,
|
|
@@ -388,6 +417,7 @@ export default class InventoryItem {
|
|
|
388
417
|
...(Object.keys(descriptiveAttributes).length > 0
|
|
389
418
|
? { descriptiveAttributes }
|
|
390
419
|
: {}),
|
|
420
|
+
...(alreadyPromoted ? { membershipOnly: true } : {}),
|
|
391
421
|
});
|
|
392
422
|
}
|
|
393
423
|
|
|
@@ -570,7 +600,32 @@ export default class InventoryItem {
|
|
|
570
600
|
return { entityType: EntityType.KubernetesNamespace, id };
|
|
571
601
|
},
|
|
572
602
|
|
|
573
|
-
|
|
603
|
+
/*
|
|
604
|
+
* k8s.node — cluster + k8s.node.name, falling back to k8s.node.uid only
|
|
605
|
+
* when no name is available.
|
|
606
|
+
*
|
|
607
|
+
* The name is preferred BECAUSE the uid is not universally reported and
|
|
608
|
+
* identity must not depend on which optional attributes a particular
|
|
609
|
+
* producer happens to carry. The shipped Kubernetes agent proves the
|
|
610
|
+
* point: its Deployment collector's `k8s_cluster` receiver reports node
|
|
611
|
+
* metrics with uid AND name, while its DaemonSet collector
|
|
612
|
+
* (kubeletstats / hostmetrics / cAdvisor / filelog) only ever has the
|
|
613
|
+
* name — the kubelet summary API carries no node uid and `k8sattributes`
|
|
614
|
+
* is not asked to extract one. A uid-first rule therefore hashed the same
|
|
615
|
+
* node two ways and put two rows in Inventory, both `discovered`, both
|
|
616
|
+
* displaying the same node name (`k8s.node.name` is a descriptive
|
|
617
|
+
* attribute on the uid-keyed row, so the two were indistinguishable in
|
|
618
|
+
* the list). Neither row aged out, because both pipelines keep reporting.
|
|
619
|
+
*
|
|
620
|
+
* Name-keying is also what the rest of OneUptime already assumes: the
|
|
621
|
+
* chart's own comment at configmap-daemonset.yaml says "OneUptime keys the
|
|
622
|
+
* k8s.node entity on k8s.cluster.name + k8s.node.name", and cluster and
|
|
623
|
+
* host identity are name-based for the same reason (see
|
|
624
|
+
* k8sClusterIdentity and the host resolver above).
|
|
625
|
+
*
|
|
626
|
+
* The uid stays available as a descriptive attribute, and remains the
|
|
627
|
+
* identity for the one resource that genuinely has nothing else.
|
|
628
|
+
*/
|
|
574
629
|
(attrs: EntityAttributes) => {
|
|
575
630
|
const nodeUid: string | null = InventoryItem.str(attrs, "k8s.node.uid");
|
|
576
631
|
const nodeName: string | null = InventoryItem.str(attrs, "k8s.node.name");
|
|
@@ -580,15 +635,27 @@ export default class InventoryItem {
|
|
|
580
635
|
const id: Dictionary<string> = {
|
|
581
636
|
...(InventoryItem.k8sClusterIdentity(attrs) || {}),
|
|
582
637
|
};
|
|
583
|
-
if (
|
|
584
|
-
id["k8s.node.uid"] = nodeUid;
|
|
585
|
-
} else if (nodeName) {
|
|
638
|
+
if (nodeName) {
|
|
586
639
|
id["k8s.node.name"] = nodeName;
|
|
640
|
+
} else {
|
|
641
|
+
id["k8s.node.uid"] = nodeUid!;
|
|
587
642
|
}
|
|
588
643
|
return { entityType: EntityType.KubernetesNode, id };
|
|
589
644
|
},
|
|
590
645
|
|
|
591
|
-
|
|
646
|
+
/*
|
|
647
|
+
* k8s.pod — cluster + namespace + k8s.pod.name, falling back to
|
|
648
|
+
* k8s.pod.uid only when no name is available. Name-preferring for the
|
|
649
|
+
* same reason as k8s.node above: a pod observed by one pipeline that
|
|
650
|
+
* resolves its uid and another that does not is one pod, not two.
|
|
651
|
+
*
|
|
652
|
+
* A pod name is unique within a namespace at any instant, so this also
|
|
653
|
+
* makes a restarted StatefulSet pod (same name, new uid) stay one
|
|
654
|
+
* inventory row instead of minting a fresh one per incarnation — which
|
|
655
|
+
* is the behaviour an inventory wants. Incarnations remain separable on
|
|
656
|
+
* the signals themselves, which still carry `k8s.pod.uid` as a resource
|
|
657
|
+
* attribute, and the 24h pod TTL still reaps a name that stops reporting.
|
|
658
|
+
*/
|
|
592
659
|
(attrs: EntityAttributes) => {
|
|
593
660
|
const podUid: string | null = InventoryItem.str(attrs, "k8s.pod.uid");
|
|
594
661
|
const podName: string | null = InventoryItem.str(attrs, "k8s.pod.name");
|
|
@@ -599,10 +666,10 @@ export default class InventoryItem {
|
|
|
599
666
|
...(InventoryItem.k8sClusterIdentity(attrs) || {}),
|
|
600
667
|
};
|
|
601
668
|
InventoryItem.addIfPresent(id, attrs, "k8s.namespace.name");
|
|
602
|
-
if (
|
|
603
|
-
id["k8s.pod.uid"] = podUid;
|
|
604
|
-
} else if (podName) {
|
|
669
|
+
if (podName) {
|
|
605
670
|
id["k8s.pod.name"] = podName;
|
|
671
|
+
} else {
|
|
672
|
+
id["k8s.pod.uid"] = podUid!;
|
|
606
673
|
}
|
|
607
674
|
return { entityType: EntityType.KubernetesPod, id };
|
|
608
675
|
},
|
|
@@ -820,11 +887,18 @@ export default class InventoryItem {
|
|
|
820
887
|
"telemetry.sdk.name",
|
|
821
888
|
"telemetry.sdk.version",
|
|
822
889
|
],
|
|
890
|
+
/*
|
|
891
|
+
* The uids are descriptive rather than identifying (see the node / pod
|
|
892
|
+
* resolvers): keeping them here means the Inventory row still shows the
|
|
893
|
+
* uid of the object it is currently tracking, without that uid being able
|
|
894
|
+
* to fork the row's identity.
|
|
895
|
+
*/
|
|
823
896
|
[EntityType.KubernetesNode]: [
|
|
824
897
|
"k8s.node.name",
|
|
898
|
+
"k8s.node.uid",
|
|
825
899
|
"node.kubernetes.io/instance-type",
|
|
826
900
|
],
|
|
827
|
-
[EntityType.KubernetesPod]: ["k8s.pod.name"],
|
|
901
|
+
[EntityType.KubernetesPod]: ["k8s.pod.name", "k8s.pod.uid"],
|
|
828
902
|
[EntityType.Container]: [
|
|
829
903
|
"container.image.name",
|
|
830
904
|
"container.image.tag",
|
|
@@ -530,3 +530,35 @@ describe("the Add to Monitoring dialog", () => {
|
|
|
530
530
|
}
|
|
531
531
|
});
|
|
532
532
|
});
|
|
533
|
+
|
|
534
|
+
/*
|
|
535
|
+
* The MAC the map already knows for a neighbour rides into the create form,
|
|
536
|
+
* so a device adopted from the drawer keeps its switch port from its first
|
|
537
|
+
* poll rather than waiting for a router's ARP table to say it again.
|
|
538
|
+
*/
|
|
539
|
+
describe("the MAC Address the map knows is pre-filled", () => {
|
|
540
|
+
beforeEach(() => {
|
|
541
|
+
captured = null;
|
|
542
|
+
neighborQueries = [];
|
|
543
|
+
neighborRows = [{ _id: "switch-1", probeId: "probe-a", siteId: "site-a" }];
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
afterEach(() => {
|
|
547
|
+
cleanup();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
test("a neighbour with a known MAC opens with it filled in", async () => {
|
|
551
|
+
const props: CapturedModalProps = await openDialog({
|
|
552
|
+
...PHONE_NODE,
|
|
553
|
+
macAddress: "aa:bb:cc:dd:ee:01",
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
expect(props.initialValues["macAddress"]).toBe("aa:bb:cc:dd:ee:01");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("a neighbour with no known MAC opens with the field empty", async () => {
|
|
560
|
+
const props: CapturedModalProps = await openDialog(PHONE_NODE);
|
|
561
|
+
|
|
562
|
+
expect(props.initialValues["macAddress"]).toBeUndefined();
|
|
563
|
+
});
|
|
564
|
+
});
|