@oneuptime/common 12.0.1 → 12.0.3
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/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +27 -45
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +24 -42
- package/Server/Services/DatabaseService.ts +198 -6
- package/Server/Services/DockerHostService.ts +20 -38
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +28 -46
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +47 -57
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +20 -38
- package/Server/Services/KubernetesClusterService.ts +18 -36
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +153 -1
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +146 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +20 -38
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +24 -42
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +20 -38
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +25 -43
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +658 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +636 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Date.ts +112 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Events/Recurring.ts +99 -1
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -39
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -36
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +155 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -32
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -40
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -64
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -32
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -30
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +127 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +128 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -32
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -36
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -32
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -37
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- 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/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
truncateLongText,
|
|
3
|
+
truncateShortText,
|
|
4
|
+
truncateToLength,
|
|
5
|
+
} from "../../../../Server/Utils/Database/TruncateColumnValue";
|
|
6
|
+
import ColumnLength from "../../../../Types/Database/ColumnLength";
|
|
7
|
+
import { describe, expect, test } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The clamp that keeps a single oversized value from aborting a 500-row
|
|
11
|
+
* bulk INSERT (issue #3006 follow-up). Everything here is about the
|
|
12
|
+
* boundary: clip one character too eagerly and legitimate values get
|
|
13
|
+
* silently corrupted; clip one too late and Postgres still raises 22001
|
|
14
|
+
* and drops the whole chunk.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
describe("truncateToLength", () => {
|
|
18
|
+
test("returns a short value untouched, by identity", () => {
|
|
19
|
+
const value: string = "nginx:1.27";
|
|
20
|
+
expect(truncateToLength(value, 100)).toBe(value);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns a value of exactly maxLength untouched", () => {
|
|
24
|
+
/*
|
|
25
|
+
* The off-by-one that matters most: varchar(100) accepts exactly
|
|
26
|
+
* 100 characters, so clipping at 100 would corrupt a legal value.
|
|
27
|
+
*/
|
|
28
|
+
const value: string = "a".repeat(100);
|
|
29
|
+
const result: string = truncateToLength(value, 100);
|
|
30
|
+
expect(result).toBe(value);
|
|
31
|
+
expect(result).toHaveLength(100);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("clips a value one character too long down to exactly maxLength", () => {
|
|
35
|
+
const value: string = "a".repeat(101);
|
|
36
|
+
const result: string = truncateToLength(value, 100);
|
|
37
|
+
expect(result).toHaveLength(100);
|
|
38
|
+
expect(value.startsWith(result)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("clips a wildly oversized value down to exactly maxLength", () => {
|
|
42
|
+
const result: string = truncateToLength("b".repeat(5000), 100);
|
|
43
|
+
expect(result).toHaveLength(100);
|
|
44
|
+
expect(result).toBe("b".repeat(100));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("the clipped value is always a prefix of the original", () => {
|
|
48
|
+
const value: string =
|
|
49
|
+
"ghcr.io/some-really-long-organization-name/some-really-long-repository-name:v1.2.3-rc.4-with-a-long-build-suffix";
|
|
50
|
+
const result: string = truncateToLength(value, 40);
|
|
51
|
+
expect(value.startsWith(result)).toBe(true);
|
|
52
|
+
expect(result).toHaveLength(40);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("passes null through as null", () => {
|
|
56
|
+
expect(truncateToLength(null, 100)).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("normalizes undefined to null, so it binds as SQL NULL", () => {
|
|
60
|
+
expect(truncateToLength(undefined, 100)).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("keeps an empty string as a string, never as null", () => {
|
|
64
|
+
/*
|
|
65
|
+
* KubernetesContainer.podNamespaceKey is NOT NULL with a "" default
|
|
66
|
+
* for cluster-scoped pods — turning "" into NULL would break the
|
|
67
|
+
* insert and the unique index that covers it.
|
|
68
|
+
*/
|
|
69
|
+
const result: string = truncateToLength("", 100);
|
|
70
|
+
expect(result).toBe("");
|
|
71
|
+
expect(result).not.toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("handles a maxLength of zero without throwing", () => {
|
|
75
|
+
expect(truncateToLength("anything", 0)).toBe("");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("multi-byte characters", () => {
|
|
79
|
+
/*
|
|
80
|
+
* Postgres counts varchar(n) in characters; JS counts string length
|
|
81
|
+
* in UTF-16 code units. Astral-plane characters (emoji, rare CJK)
|
|
82
|
+
* are two code units each, so clipping by code units is always
|
|
83
|
+
* conservative — but the cut must not land inside a surrogate pair.
|
|
84
|
+
*/
|
|
85
|
+
test("never emits more characters than the column allows", () => {
|
|
86
|
+
const value: string = "😀".repeat(200); // 400 code units, 200 chars
|
|
87
|
+
const result: string = truncateToLength(value, 100);
|
|
88
|
+
|
|
89
|
+
expect(result.length).toBeLessThanOrEqual(100);
|
|
90
|
+
expect(Array.from(result).length).toBeLessThanOrEqual(100);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("does not split a surrogate pair when the cut lands inside one", () => {
|
|
94
|
+
/*
|
|
95
|
+
* 99 ASCII + one 2-code-unit emoji = 101 code units; cut at 100
|
|
96
|
+
* would otherwise land between the emoji's two halves.
|
|
97
|
+
*/
|
|
98
|
+
const value: string = `${"a".repeat(99)}😀`;
|
|
99
|
+
const result: string = truncateToLength(value, 100);
|
|
100
|
+
|
|
101
|
+
expect(result).toBe("a".repeat(99));
|
|
102
|
+
expect(result).not.toContain("\ud83d");
|
|
103
|
+
// A lone surrogate survives a round trip through UTF-8 as U+FFFD.
|
|
104
|
+
expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("keeps a whole surrogate pair when it fits exactly", () => {
|
|
108
|
+
const value: string = `${"a".repeat(98)}😀b`; // 101 code units
|
|
109
|
+
const result: string = truncateToLength(value, 100);
|
|
110
|
+
|
|
111
|
+
expect(result).toBe(`${"a".repeat(98)}😀`);
|
|
112
|
+
expect(result).toHaveLength(100);
|
|
113
|
+
expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("leaves a multi-byte value that fits completely alone", () => {
|
|
117
|
+
const value: string = "café-über-日本語";
|
|
118
|
+
expect(truncateToLength(value, 100)).toBe(value);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("truncateShortText", () => {
|
|
124
|
+
test("clamps at ColumnLength.ShortText", () => {
|
|
125
|
+
expect(truncateShortText("a".repeat(500))).toHaveLength(
|
|
126
|
+
ColumnLength.ShortText,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("ColumnLength.ShortText is still 100, the varchar width it mirrors", () => {
|
|
131
|
+
expect(ColumnLength.ShortText).toBe(100);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("agrees with truncateToLength at the same bound", () => {
|
|
135
|
+
const value: string = "z".repeat(250);
|
|
136
|
+
expect(truncateShortText(value)).toBe(
|
|
137
|
+
truncateToLength(value, ColumnLength.ShortText),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("passes null and short values straight through", () => {
|
|
142
|
+
expect(truncateShortText(null)).toBeNull();
|
|
143
|
+
expect(truncateShortText("osd.3")).toBe("osd.3");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("truncateLongText", () => {
|
|
148
|
+
test("clamps at ColumnLength.LongText", () => {
|
|
149
|
+
expect(truncateLongText("a".repeat(5000))).toHaveLength(
|
|
150
|
+
ColumnLength.LongText,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("ColumnLength.LongText is still 500, the varchar width it mirrors", () => {
|
|
155
|
+
expect(ColumnLength.LongText).toBe(500);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("accepts a 253-character Kubernetes DNS-subdomain name untouched", () => {
|
|
159
|
+
/*
|
|
160
|
+
* The whole point of widening the name columns: the longest name
|
|
161
|
+
* Kubernetes itself will ever produce has to pass through unclipped.
|
|
162
|
+
*/
|
|
163
|
+
const podName: string = "p".repeat(253);
|
|
164
|
+
expect(truncateLongText(podName)).toBe(podName);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("is strictly more permissive than truncateShortText", () => {
|
|
168
|
+
const value: string = "c".repeat(400);
|
|
169
|
+
expect(truncateLongText(value)).toBe(value);
|
|
170
|
+
expect(truncateShortText(value)).toHaveLength(ColumnLength.ShortText);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("passes null and short values straight through", () => {
|
|
174
|
+
expect(truncateLongText(null)).toBeNull();
|
|
175
|
+
expect(truncateLongText("ghcr.io/acme/api:1.2.3")).toBe(
|
|
176
|
+
"ghcr.io/acme/api:1.2.3",
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import GlobalCache from "../../../Server/Infrastructure/GlobalCache";
|
|
2
|
+
import TelemetryEntityService from "../../../Server/Services/TelemetryEntityService";
|
|
3
|
+
import { reconcileByNaturalKey } from "../../../Server/Utils/Telemetry/EntityRegistry";
|
|
4
|
+
import TelemetryEntity from "../../../Models/DatabaseModels/TelemetryEntity";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
import {
|
|
7
|
+
afterEach,
|
|
8
|
+
beforeEach,
|
|
9
|
+
describe,
|
|
10
|
+
expect,
|
|
11
|
+
jest,
|
|
12
|
+
test,
|
|
13
|
+
} from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* The entity registry had the same defect as the Service heartbeat, one layer
|
|
17
|
+
* down and harder to see.
|
|
18
|
+
*
|
|
19
|
+
* Its fence keys on (project + the whole set of promoted entity keys), which
|
|
20
|
+
* in practice is unique per POD — a pod's own key is in the set. But the
|
|
21
|
+
* writes it gates are per ROW: the single TelemetryEntity row for a Kubernetes
|
|
22
|
+
* cluster receives one UPDATE per pod in that cluster per window, the
|
|
23
|
+
* namespace row one per pod in the namespace. When throttle granularity is
|
|
24
|
+
* finer than write granularity, the throttle does not bound the writes at all,
|
|
25
|
+
* and the mismatch factor is the pod count.
|
|
26
|
+
*
|
|
27
|
+
* So there are now two fences: the cheap set-level one, and a per-row one
|
|
28
|
+
* claimed atomically before the lookup — which also removes the redundant
|
|
29
|
+
* SELECT, not just the redundant UPDATE. The bump itself is the SKIP LOCKED
|
|
30
|
+
* write, so shared rows (a cluster, a namespace) cannot convoy the way Service
|
|
31
|
+
* did.
|
|
32
|
+
*
|
|
33
|
+
* Everything external is mocked — no Postgres, no Redis.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
37
|
+
const ROW_ID: ObjectID = ObjectID.generate();
|
|
38
|
+
|
|
39
|
+
type BumpCall = { id: ObjectID; data: Record<string, unknown> };
|
|
40
|
+
|
|
41
|
+
let cache: Map<string, string>;
|
|
42
|
+
let deletedKeys: Array<string>;
|
|
43
|
+
let bumps: Array<BumpCall>;
|
|
44
|
+
let reads: number;
|
|
45
|
+
let existing: TelemetryEntity | null;
|
|
46
|
+
let bumpLands: boolean;
|
|
47
|
+
|
|
48
|
+
function existingRow(): TelemetryEntity {
|
|
49
|
+
const row: TelemetryEntity = new TelemetryEntity();
|
|
50
|
+
row.id = ROW_ID;
|
|
51
|
+
row._id = ROW_ID.toString();
|
|
52
|
+
return row;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function reconcile(rowFenceId: string): Promise<void> {
|
|
56
|
+
return reconcileByNaturalKey<TelemetryEntity>({
|
|
57
|
+
service: TelemetryEntityService,
|
|
58
|
+
query: { projectId: PROJECT_ID, entityKey: "k8s-cluster/prod" },
|
|
59
|
+
lastSeenAt: new Date(),
|
|
60
|
+
describe: "entity k8s-cluster/prod",
|
|
61
|
+
rowFenceId: rowFenceId,
|
|
62
|
+
buildModel: (): TelemetryEntity => {
|
|
63
|
+
return existingRow();
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
beforeEach(() => {
|
|
69
|
+
cache = new Map<string, string>();
|
|
70
|
+
deletedKeys = [];
|
|
71
|
+
bumps = [];
|
|
72
|
+
reads = 0;
|
|
73
|
+
existing = existingRow();
|
|
74
|
+
bumpLands = true;
|
|
75
|
+
|
|
76
|
+
jest
|
|
77
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
78
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
79
|
+
const full: string = `${namespace}:${key}`;
|
|
80
|
+
if (cache.has(full)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
cache.set(full, "1");
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
jest
|
|
88
|
+
.spyOn(GlobalCache, "deleteKey")
|
|
89
|
+
.mockImplementation(async (namespace: string, key: string) => {
|
|
90
|
+
const full: string = `${namespace}:${key}`;
|
|
91
|
+
deletedKeys.push(full);
|
|
92
|
+
cache.delete(full);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
jest
|
|
96
|
+
.spyOn(TelemetryEntityService, "findOneBy")
|
|
97
|
+
.mockImplementation(async () => {
|
|
98
|
+
reads++;
|
|
99
|
+
return existing;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
jest
|
|
103
|
+
.spyOn(TelemetryEntityService, "updateColumnsByIdIfUnlockedWithoutHooks")
|
|
104
|
+
.mockImplementation(async (input: { id: ObjectID; data: unknown }) => {
|
|
105
|
+
bumps.push({
|
|
106
|
+
id: input.id,
|
|
107
|
+
data: { ...(input.data as Record<string, unknown>) },
|
|
108
|
+
});
|
|
109
|
+
return bumpLands;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
jest
|
|
113
|
+
.spyOn(TelemetryEntityService, "create")
|
|
114
|
+
.mockImplementation(async (input: { data: TelemetryEntity }) => {
|
|
115
|
+
return input.data;
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
jest.restoreAllMocks();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("reconcileByNaturalKey — the per-row fence", () => {
|
|
124
|
+
test("bumps the row on first contact", async () => {
|
|
125
|
+
await reconcile("project:k8s-cluster:prod");
|
|
126
|
+
|
|
127
|
+
expect(bumps).toHaveLength(1);
|
|
128
|
+
expect(bumps[0]!.id.toString()).toBe(ROW_ID.toString());
|
|
129
|
+
expect(bumps[0]!.data["lastSeenAt"]).toBeInstanceOf(Date);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
/*
|
|
133
|
+
* The defect, reproduced: many pods, one shared row. Before the per-row
|
|
134
|
+
* fence, each pod's distinct set-level fence let it straight through to the
|
|
135
|
+
* same cluster row.
|
|
136
|
+
*/
|
|
137
|
+
test("many callers for the same row produce one bump per window", async () => {
|
|
138
|
+
for (let pod: number = 0; pod < 50; pod++) {
|
|
139
|
+
await reconcile("project:k8s-cluster:prod");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
expect(bumps).toHaveLength(1);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/*
|
|
146
|
+
* Claimed BEFORE the lookup. The redundant SELECTs were as much of the pool
|
|
147
|
+
* pressure as the redundant UPDATEs.
|
|
148
|
+
*/
|
|
149
|
+
test("a fenced caller does not even read the row", async () => {
|
|
150
|
+
await reconcile("project:k8s-cluster:prod");
|
|
151
|
+
const readsAfterFirst: number = reads;
|
|
152
|
+
|
|
153
|
+
await reconcile("project:k8s-cluster:prod");
|
|
154
|
+
|
|
155
|
+
expect(reads).toBe(readsAfterFirst);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("different rows are fenced independently", async () => {
|
|
159
|
+
await reconcile("project:k8s-cluster:prod");
|
|
160
|
+
await reconcile("project:k8s-namespace:default");
|
|
161
|
+
|
|
162
|
+
expect(bumps).toHaveLength(2);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("claims the fence atomically, never read-then-write", async () => {
|
|
166
|
+
jest.spyOn(GlobalCache, "getString");
|
|
167
|
+
jest.spyOn(GlobalCache, "setString");
|
|
168
|
+
|
|
169
|
+
await reconcile("project:k8s-cluster:prod");
|
|
170
|
+
|
|
171
|
+
expect(GlobalCache.setStringIfNotExists).toHaveBeenCalled();
|
|
172
|
+
expect(GlobalCache.getString).not.toHaveBeenCalled();
|
|
173
|
+
expect(GlobalCache.setString).not.toHaveBeenCalled();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* The fence id concatenates entity keys and is unbounded in length. Hashing
|
|
178
|
+
* keeps the Redis key fixed-width — a pod reporting hundreds of entities
|
|
179
|
+
* would otherwise produce a multi-kilobyte key.
|
|
180
|
+
*/
|
|
181
|
+
test("hashes the fence id to a fixed-width key", async () => {
|
|
182
|
+
const longId: string = `project:${"k".repeat(5000)}`;
|
|
183
|
+
|
|
184
|
+
await reconcile(longId);
|
|
185
|
+
|
|
186
|
+
const key: string = (GlobalCache.setStringIfNotExists as jest.Mock).mock
|
|
187
|
+
.calls[0]![1] as string;
|
|
188
|
+
|
|
189
|
+
expect(key.length).toBeLessThan(100);
|
|
190
|
+
expect(key).not.toContain("kkkk");
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("a cache outage fails open so liveness still advances", async () => {
|
|
194
|
+
jest
|
|
195
|
+
.spyOn(GlobalCache, "setStringIfNotExists")
|
|
196
|
+
.mockRejectedValue(new Error("redis down"));
|
|
197
|
+
|
|
198
|
+
await reconcile("project:k8s-cluster:prod");
|
|
199
|
+
|
|
200
|
+
expect(bumps).toHaveLength(1);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe("reconcileByNaturalKey — contention and failure re-open the window", () => {
|
|
205
|
+
test("uses the non-blocking write primitive", async () => {
|
|
206
|
+
await reconcile("project:k8s-cluster:prod");
|
|
207
|
+
|
|
208
|
+
expect(
|
|
209
|
+
TelemetryEntityService.updateColumnsByIdIfUnlockedWithoutHooks,
|
|
210
|
+
).toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
/*
|
|
214
|
+
* A skipped bump means the row was locked, so nothing was written — but the
|
|
215
|
+
* fence is already claimed and would suppress the next attempt for the whole
|
|
216
|
+
* window. Release it so the next batch retries; each attempt is
|
|
217
|
+
* non-blocking, so this cannot become a retry storm.
|
|
218
|
+
*/
|
|
219
|
+
test("a skipped bump releases the row fence", async () => {
|
|
220
|
+
bumpLands = false;
|
|
221
|
+
|
|
222
|
+
await reconcile("project:k8s-cluster:prod");
|
|
223
|
+
|
|
224
|
+
expect(deletedKeys).toHaveLength(1);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("the released fence actually lets the next attempt through", async () => {
|
|
228
|
+
bumpLands = false;
|
|
229
|
+
await reconcile("project:k8s-cluster:prod");
|
|
230
|
+
|
|
231
|
+
bumpLands = true;
|
|
232
|
+
await reconcile("project:k8s-cluster:prod");
|
|
233
|
+
|
|
234
|
+
expect(bumps).toHaveLength(2);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/*
|
|
238
|
+
* An over-budget skip writes nothing, so holding the window would suppress
|
|
239
|
+
* the gate's own re-evaluation for a decision that had no effect.
|
|
240
|
+
*/
|
|
241
|
+
test("an over-budget create releases the row fence", async () => {
|
|
242
|
+
existing = null;
|
|
243
|
+
|
|
244
|
+
await reconcileByNaturalKey<TelemetryEntity>({
|
|
245
|
+
service: TelemetryEntityService,
|
|
246
|
+
query: { projectId: PROJECT_ID, entityKey: "k8s-cluster/prod" },
|
|
247
|
+
lastSeenAt: new Date(),
|
|
248
|
+
describe: "entity k8s-cluster/prod",
|
|
249
|
+
rowFenceId: "project:k8s-cluster:prod",
|
|
250
|
+
buildModel: (): TelemetryEntity => {
|
|
251
|
+
return existingRow();
|
|
252
|
+
},
|
|
253
|
+
beforeCreate: async (): Promise<boolean> => {
|
|
254
|
+
return false;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
expect(deletedKeys).toHaveLength(1);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
test("an invalid create releases the row fence so the warning keeps surfacing", async () => {
|
|
262
|
+
existing = null;
|
|
263
|
+
|
|
264
|
+
jest
|
|
265
|
+
.spyOn(TelemetryEntityService, "create")
|
|
266
|
+
.mockRejectedValue(new Error("null value in column violates not-null"));
|
|
267
|
+
|
|
268
|
+
await reconcile("project:k8s-cluster:prod");
|
|
269
|
+
|
|
270
|
+
expect(deletedKeys).toHaveLength(1);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("a lost create race bumps the winner instead of failing", async () => {
|
|
274
|
+
existing = null;
|
|
275
|
+
|
|
276
|
+
jest
|
|
277
|
+
.spyOn(TelemetryEntityService, "create")
|
|
278
|
+
.mockRejectedValue(new Error("duplicate key value violates unique"));
|
|
279
|
+
|
|
280
|
+
jest
|
|
281
|
+
.spyOn(TelemetryEntityService, "findOneBy")
|
|
282
|
+
.mockImplementationOnce(async () => {
|
|
283
|
+
reads++;
|
|
284
|
+
return null;
|
|
285
|
+
})
|
|
286
|
+
.mockImplementation(async () => {
|
|
287
|
+
reads++;
|
|
288
|
+
return existingRow();
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
await reconcile("project:k8s-cluster:prod");
|
|
292
|
+
|
|
293
|
+
expect(bumps).toHaveLength(1);
|
|
294
|
+
expect(deletedKeys).toHaveLength(0);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
test("a successful bump leaves the fence claimed", async () => {
|
|
298
|
+
await reconcile("project:k8s-cluster:prod");
|
|
299
|
+
|
|
300
|
+
expect(deletedKeys).toHaveLength(0);
|
|
301
|
+
});
|
|
302
|
+
});
|