@oneuptime/common 12.0.1 → 12.0.2
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/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Server/API/TeamMemberAPI.ts +85 -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/Index.ts +8 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +47 -5
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +47 -5
- package/Server/Services/DatabaseService.ts +87 -6
- package/Server/Services/DockerHostService.ts +47 -5
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +48 -5
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +48 -5
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +47 -5
- package/Server/Services/KubernetesClusterService.ts +48 -5
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +106 -1
- package/Server/Services/MonitorService.ts +57 -0
- package/Server/Services/PodmanHostService.ts +47 -5
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +47 -5
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +48 -5
- package/Server/Services/ServerlessFunctionService.ts +48 -5
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -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/TruncateColumnValue.ts +84 -0
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -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/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -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/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -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/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -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/Date.ts +112 -0
- package/Types/Events/Recurring.ts +99 -1
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -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/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.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/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/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.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/Index.js +8 -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/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +42 -5
- 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 +42 -5
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +69 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +42 -5
- 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 +43 -5
- 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 +43 -5
- package/build/dist/Server/Services/HostService.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 +42 -5
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
- 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 +92 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +47 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +42 -5
- 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 +42 -5
- 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 +43 -5
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
- 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/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/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/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.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/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.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/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/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
|
@@ -243,11 +243,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
246
|
-
const
|
|
246
|
+
const liveness: any = {
|
|
247
247
|
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
248
248
|
otelCollectorStatus: "connected",
|
|
249
249
|
};
|
|
250
250
|
|
|
251
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
+
const data: any = { ...liveness };
|
|
253
|
+
|
|
251
254
|
if (extra?.osType) {
|
|
252
255
|
data.osType = extra.osType;
|
|
253
256
|
}
|
|
@@ -313,11 +316,51 @@ export class Service extends DatabaseService<Model> {
|
|
|
313
316
|
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
314
317
|
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
315
318
|
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
319
|
+
*
|
|
320
|
+
* Every optional value above comes from an OpenTelemetry resource
|
|
321
|
+
* attribute, so its length is whatever the collector felt like sending.
|
|
322
|
+
* updateColumnsByIdWithoutHooks clamps them to their declared column
|
|
323
|
+
* widths for us — see truncateStringColumnsToMaxLength.
|
|
316
324
|
*/
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
325
|
+
try {
|
|
326
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
327
|
+
id: hostId,
|
|
328
|
+
data: data,
|
|
329
|
+
});
|
|
330
|
+
} catch (err) {
|
|
331
|
+
/*
|
|
332
|
+
* Liveness must survive bad metadata. The enriched write carries
|
|
333
|
+
* `lastSeenAt` and `otelCollectorStatus` alongside a dozen optional,
|
|
334
|
+
* collector-supplied columns; if any one of them makes Postgres
|
|
335
|
+
* reject the statement, the host silently stops advancing
|
|
336
|
+
* lastSeenAt and MarkDisconnectedHosts flips a perfectly healthy
|
|
337
|
+
* host to "disconnected" 15 minutes later — with telemetry still
|
|
338
|
+
* arriving the whole time. That was issue #3006 (a host.ip list
|
|
339
|
+
* longer than the then-varchar(500) column). Retry with liveness
|
|
340
|
+
* only so the enrichment is what gets dropped, never the heartbeat.
|
|
341
|
+
*/
|
|
342
|
+
logger.warn(
|
|
343
|
+
`HostService.updateLastSeen: metadata write failed for host ${hostId.toString()}, falling back to a liveness-only update: ${
|
|
344
|
+
err instanceof Error ? err.message : String(err)
|
|
345
|
+
}`,
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* The throttle fingerprint was already stored, so without this the
|
|
350
|
+
* next batch would short-circuit on a cache hit and skip the retry
|
|
351
|
+
* for the rest of the window.
|
|
352
|
+
*/
|
|
353
|
+
try {
|
|
354
|
+
await GlobalCache.deleteKey(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
355
|
+
} catch {
|
|
356
|
+
// Best-effort; the fallback write below is what actually matters.
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
360
|
+
id: hostId,
|
|
361
|
+
data: liveness,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
321
364
|
}
|
|
322
365
|
|
|
323
366
|
private fingerprintExtras(extra?: {
|
|
@@ -5,6 +5,7 @@ import ColumnLength from "../../Types/Database/ColumnLength";
|
|
|
5
5
|
import ObjectID from "../../Types/ObjectID";
|
|
6
6
|
import OneUptimeDate from "../../Types/Date";
|
|
7
7
|
import { clampIoTTimestamp } from "../Utils/Telemetry/IoTSnapshotScan";
|
|
8
|
+
import { truncateShortText } from "../Utils/Database/TruncateColumnValue";
|
|
8
9
|
import logger from "../Utils/Logger";
|
|
9
10
|
|
|
10
11
|
/*
|
|
@@ -59,23 +60,6 @@ export interface IoTInventorySummary {
|
|
|
59
60
|
const UPSERT_BATCH_SIZE: number = 500;
|
|
60
61
|
const STALE_DELETE_WARN_THRESHOLD: number = 100;
|
|
61
62
|
|
|
62
|
-
/*
|
|
63
|
-
* The identity/text columns are ShortText (100 chars). A single value
|
|
64
|
-
* over the column limit fails the whole multi-row INSERT chunk it rides
|
|
65
|
-
* in — truncate per value instead so one malformed device can never
|
|
66
|
-
* drop the other rows of its chunk.
|
|
67
|
-
*/
|
|
68
|
-
function truncateShortText(value: string): string;
|
|
69
|
-
function truncateShortText(value: string | null): string | null;
|
|
70
|
-
function truncateShortText(value: string | null): string | null {
|
|
71
|
-
if (value === null) {
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
return value.length > ColumnLength.ShortText
|
|
75
|
-
? value.substring(0, ColumnLength.ShortText)
|
|
76
|
-
: value;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
63
|
/*
|
|
80
64
|
* Column order used by bulkUpsert() and its generated parameter tuples.
|
|
81
65
|
* Keep this and the INSERT column list in perfect sync.
|
|
@@ -214,11 +214,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
217
|
-
const
|
|
217
|
+
const liveness: any = {
|
|
218
218
|
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
219
219
|
otelCollectorStatus: "connected",
|
|
220
220
|
};
|
|
221
221
|
|
|
222
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
223
|
+
const data: any = { ...liveness };
|
|
224
|
+
|
|
222
225
|
if (extra?.agentVersion) {
|
|
223
226
|
data.agentVersion = extra.agentVersion;
|
|
224
227
|
}
|
|
@@ -234,11 +237,50 @@ export class Service extends DatabaseService<Model> {
|
|
|
234
237
|
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
235
238
|
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
236
239
|
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
240
|
+
*
|
|
241
|
+
* Collector-supplied strings are clamped to their declared column
|
|
242
|
+
* widths inside updateColumnsByIdWithoutHooks — see
|
|
243
|
+
* DatabaseService.truncateStringColumnsToMaxLength.
|
|
237
244
|
*/
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
245
|
+
try {
|
|
246
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
247
|
+
id: fleetId,
|
|
248
|
+
data: data,
|
|
249
|
+
});
|
|
250
|
+
} catch (err) {
|
|
251
|
+
/*
|
|
252
|
+
* Liveness must survive bad metadata. The enriched write carries
|
|
253
|
+
* `lastSeenAt` and `otelCollectorStatus` alongside optional,
|
|
254
|
+
* collector-supplied columns; if any one of them makes Postgres
|
|
255
|
+
* reject the statement, lastSeenAt silently stops advancing and
|
|
256
|
+
* markDisconnectedFleets flips a perfectly healthy IoT fleet to
|
|
257
|
+
* "disconnected" 15 minutes later — with telemetry still arriving the
|
|
258
|
+
* whole time. That was issue #3006 on the Host path (a host.ip list
|
|
259
|
+
* longer than the then-varchar(500) column). Retry with liveness only
|
|
260
|
+
* so the enrichment is what gets dropped, never the heartbeat.
|
|
261
|
+
*/
|
|
262
|
+
logger.warn(
|
|
263
|
+
`IoTFleetService.updateLastSeen: metadata write failed for IoT fleet ${fleetId.toString()}, falling back to a liveness-only update: ${
|
|
264
|
+
err instanceof Error ? err.message : String(err)
|
|
265
|
+
}`,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
/*
|
|
269
|
+
* The throttle fingerprint was already stored, so without this the
|
|
270
|
+
* next batch would short-circuit on a cache hit and skip the retry
|
|
271
|
+
* for the rest of the window.
|
|
272
|
+
*/
|
|
273
|
+
try {
|
|
274
|
+
await GlobalCache.deleteKey(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
275
|
+
} catch {
|
|
276
|
+
// Best-effort; the fallback write below is what actually matters.
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
280
|
+
id: fleetId,
|
|
281
|
+
data: liveness,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
242
284
|
}
|
|
243
285
|
|
|
244
286
|
/**
|
|
@@ -184,11 +184,14 @@ export class Service extends DatabaseService<Model> {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
187
|
-
const
|
|
187
|
+
const liveness: any = {
|
|
188
188
|
lastSeenAt: OneUptimeDate.getCurrentDate(),
|
|
189
189
|
otelCollectorStatus: "connected",
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
193
|
+
const data: any = { ...liveness };
|
|
194
|
+
|
|
192
195
|
if (extra?.agentVersion) {
|
|
193
196
|
data.agentVersion = extra.agentVersion;
|
|
194
197
|
}
|
|
@@ -197,11 +200,51 @@ export class Service extends DatabaseService<Model> {
|
|
|
197
200
|
* Heartbeat write: a single-statement UPDATE with no hooks and no
|
|
198
201
|
* `version` bump, avoiding the hot-row Postgres lock convoy that the
|
|
199
202
|
* full updateOneById pipeline causes. See ServiceService.updateLastSeen.
|
|
203
|
+
*
|
|
204
|
+
* Collector-supplied strings are clamped to their declared column
|
|
205
|
+
* widths inside updateColumnsByIdWithoutHooks — see
|
|
206
|
+
* DatabaseService.truncateStringColumnsToMaxLength.
|
|
200
207
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
208
|
+
try {
|
|
209
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
210
|
+
id: clusterId,
|
|
211
|
+
data: data,
|
|
212
|
+
});
|
|
213
|
+
} catch (err) {
|
|
214
|
+
/*
|
|
215
|
+
* Liveness must survive bad metadata. The enriched write carries
|
|
216
|
+
* `lastSeenAt` and `otelCollectorStatus` alongside optional,
|
|
217
|
+
* collector-supplied columns; if any one of them makes Postgres
|
|
218
|
+
* reject the statement, lastSeenAt silently stops advancing and
|
|
219
|
+
* markDisconnectedClusters flips a perfectly healthy Kubernetes
|
|
220
|
+
* cluster to "disconnected" 15 minutes later — with telemetry still
|
|
221
|
+
* arriving the whole time. That was issue #3006 on the Host path (a
|
|
222
|
+
* host.ip list longer than the then-varchar(500) column). Retry with
|
|
223
|
+
* liveness only so the enrichment is what gets dropped, never the
|
|
224
|
+
* heartbeat.
|
|
225
|
+
*/
|
|
226
|
+
logger.warn(
|
|
227
|
+
`KubernetesClusterService.updateLastSeen: metadata write failed for Kubernetes cluster ${clusterId.toString()}, falling back to a liveness-only update: ${
|
|
228
|
+
err instanceof Error ? err.message : String(err)
|
|
229
|
+
}`,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
/*
|
|
233
|
+
* The throttle fingerprint was already stored, so without this the
|
|
234
|
+
* next batch would short-circuit on a cache hit and skip the retry
|
|
235
|
+
* for the rest of the window.
|
|
236
|
+
*/
|
|
237
|
+
try {
|
|
238
|
+
await GlobalCache.deleteKey(LAST_SEEN_CACHE_NAMESPACE, cacheKey);
|
|
239
|
+
} catch {
|
|
240
|
+
// Best-effort; the fallback write below is what actually matters.
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
await this.updateColumnsByIdWithoutHooks({
|
|
244
|
+
id: clusterId,
|
|
245
|
+
data: liveness,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
205
248
|
}
|
|
206
249
|
|
|
207
250
|
/**
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import Model from "../../Models/DatabaseModels/KubernetesContainer";
|
|
3
3
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
4
|
+
import ColumnLength from "../../Types/Database/ColumnLength";
|
|
4
5
|
import ObjectID from "../../Types/ObjectID";
|
|
6
|
+
import {
|
|
7
|
+
truncateLongText,
|
|
8
|
+
truncateShortText,
|
|
9
|
+
} from "../Utils/Database/TruncateColumnValue";
|
|
5
10
|
import logger from "../Utils/Logger";
|
|
6
11
|
|
|
7
12
|
/*
|
|
@@ -61,6 +66,29 @@ const UPSERT_COLUMNS: Array<string> = [
|
|
|
61
66
|
"version",
|
|
62
67
|
];
|
|
63
68
|
|
|
69
|
+
/*
|
|
70
|
+
* KubernetesContainer's text columns are bounded varchars — podName and
|
|
71
|
+
* image at 500 chars, podNamespaceKey / name / state / reason at 100.
|
|
72
|
+
* The bulk paths below go through manager.query(), which skips the
|
|
73
|
+
* length validation DatabaseService applies to ordinary writes, so a
|
|
74
|
+
* single oversized value aborts the whole 500-row INSERT chunk it rides
|
|
75
|
+
* in. Clamp per value instead, so one pathological pod can never drop
|
|
76
|
+
* the rest of the snapshot.
|
|
77
|
+
*/
|
|
78
|
+
function sanitizeContainer(
|
|
79
|
+
c: ParsedKubernetesContainer,
|
|
80
|
+
): ParsedKubernetesContainer {
|
|
81
|
+
return {
|
|
82
|
+
...c,
|
|
83
|
+
podNamespaceKey: truncateShortText(c.podNamespaceKey),
|
|
84
|
+
podName: truncateLongText(c.podName),
|
|
85
|
+
name: truncateShortText(c.name),
|
|
86
|
+
image: truncateLongText(c.image),
|
|
87
|
+
state: truncateShortText(c.state),
|
|
88
|
+
reason: truncateShortText(c.reason),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
64
92
|
export class Service extends DatabaseService<Model> {
|
|
65
93
|
public constructor() {
|
|
66
94
|
super(Model);
|
|
@@ -86,12 +114,20 @@ export class Service extends DatabaseService<Model> {
|
|
|
86
114
|
return;
|
|
87
115
|
}
|
|
88
116
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
117
|
+
const containers: Array<ParsedKubernetesContainer> = data.containers.map(
|
|
118
|
+
(c: ParsedKubernetesContainer) => {
|
|
119
|
+
const sanitized: ParsedKubernetesContainer = sanitizeContainer(c);
|
|
120
|
+
if (sanitized.podName !== c.podName) {
|
|
121
|
+
logger.warn(
|
|
122
|
+
`KubernetesContainer podName exceeds ${ColumnLength.LongText} chars; truncated to "${sanitized.podName}" (cluster ${data.kubernetesClusterId.toString()}).`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return sanitized;
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
for (let i: number = 0; i < containers.length; i += UPSERT_BATCH_SIZE) {
|
|
130
|
+
const chunk: Array<ParsedKubernetesContainer> = containers.slice(
|
|
95
131
|
i,
|
|
96
132
|
i + UPSERT_BATCH_SIZE,
|
|
97
133
|
);
|
|
@@ -224,10 +260,15 @@ export class Service extends DatabaseService<Model> {
|
|
|
224
260
|
valueFragments.push(
|
|
225
261
|
`($${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}::numeric, $${paramIndex++}::bigint, $${paramIndex++}::timestamptz)`,
|
|
226
262
|
);
|
|
263
|
+
/*
|
|
264
|
+
* The identity clamps MUST match bulkUpsert's, or this mirror
|
|
265
|
+
* UPDATE would silently miss the row the upsert just wrote
|
|
266
|
+
* under the truncated podName.
|
|
267
|
+
*/
|
|
227
268
|
params.push(
|
|
228
|
-
m.podNamespaceKey,
|
|
229
|
-
m.podName,
|
|
230
|
-
m.name,
|
|
269
|
+
truncateShortText(m.podNamespaceKey),
|
|
270
|
+
truncateLongText(m.podName),
|
|
271
|
+
truncateShortText(m.name),
|
|
231
272
|
m.cpuPercent !== null && m.cpuPercent !== undefined
|
|
232
273
|
? m.cpuPercent
|
|
233
274
|
: null,
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import DatabaseService from "./DatabaseService";
|
|
2
2
|
import Model from "../../Models/DatabaseModels/KubernetesResource";
|
|
3
3
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
4
|
+
import ColumnLength from "../../Types/Database/ColumnLength";
|
|
4
5
|
import ObjectID from "../../Types/ObjectID";
|
|
5
6
|
import OneUptimeDate from "../../Types/Date";
|
|
6
7
|
import { ParsedKubernetesResource } from "../../Types/Kubernetes/KubernetesInventoryExtractor";
|
|
8
|
+
import {
|
|
9
|
+
truncateLongText,
|
|
10
|
+
truncateShortText,
|
|
11
|
+
} from "../Utils/Database/TruncateColumnValue";
|
|
7
12
|
import logger from "../Utils/Logger";
|
|
8
13
|
|
|
9
14
|
/*
|
|
@@ -317,6 +322,28 @@ const UPSERT_COLUMNS: Array<keyof ParsedKubernetesResource | string> = [
|
|
|
317
322
|
"version",
|
|
318
323
|
];
|
|
319
324
|
|
|
325
|
+
/*
|
|
326
|
+
* KubernetesResource's text columns are bounded varchars — name and
|
|
327
|
+
* controllerDeploymentName at 500 chars, kind / namespaceKey / uid /
|
|
328
|
+
* phase at 100. The bulk paths below go through manager.query(), which
|
|
329
|
+
* skips the length validation DatabaseService applies to ordinary
|
|
330
|
+
* writes, so a single oversized value aborts the whole 500-row INSERT
|
|
331
|
+
* chunk it rides in. Clamp per value instead, so one pathological
|
|
332
|
+
* object can never drop the rest of the snapshot.
|
|
333
|
+
*/
|
|
334
|
+
function sanitizeResource(
|
|
335
|
+
r: ParsedKubernetesResource,
|
|
336
|
+
): ParsedKubernetesResource {
|
|
337
|
+
return {
|
|
338
|
+
...r,
|
|
339
|
+
kind: truncateShortText(r.kind),
|
|
340
|
+
namespaceKey: truncateShortText(r.namespaceKey),
|
|
341
|
+
name: truncateLongText(r.name),
|
|
342
|
+
uid: truncateShortText(r.uid),
|
|
343
|
+
phase: truncateShortText(r.phase),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
320
347
|
export class Service extends DatabaseService<Model> {
|
|
321
348
|
public constructor() {
|
|
322
349
|
super(Model);
|
|
@@ -338,9 +365,21 @@ export class Service extends DatabaseService<Model> {
|
|
|
338
365
|
return;
|
|
339
366
|
}
|
|
340
367
|
|
|
368
|
+
const resources: Array<ParsedKubernetesResource> = data.resources.map(
|
|
369
|
+
(r: ParsedKubernetesResource) => {
|
|
370
|
+
const sanitized: ParsedKubernetesResource = sanitizeResource(r);
|
|
371
|
+
if (sanitized.name !== r.name) {
|
|
372
|
+
logger.warn(
|
|
373
|
+
`KubernetesResource name exceeds ${ColumnLength.LongText} chars; truncated to "${sanitized.name}" (cluster ${data.kubernetesClusterId.toString()}).`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return sanitized;
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
|
|
341
380
|
// Chunk to keep individual statement parameter counts reasonable.
|
|
342
|
-
for (let i: number = 0; i <
|
|
343
|
-
const chunk: Array<ParsedKubernetesResource> =
|
|
381
|
+
for (let i: number = 0; i < resources.length; i += UPSERT_BATCH_SIZE) {
|
|
382
|
+
const chunk: Array<ParsedKubernetesResource> = resources.slice(
|
|
344
383
|
i,
|
|
345
384
|
i + UPSERT_BATCH_SIZE,
|
|
346
385
|
);
|
|
@@ -450,10 +489,17 @@ export class Service extends DatabaseService<Model> {
|
|
|
450
489
|
valueFragments.push(
|
|
451
490
|
`($${paramIndex++}, $${paramIndex++}, $${paramIndex++}, $${paramIndex++}::numeric, $${paramIndex++}::bigint, $${paramIndex++}::numeric, $${paramIndex++}::timestamptz, $${paramIndex++}, $${paramIndex++})`,
|
|
452
491
|
);
|
|
492
|
+
/*
|
|
493
|
+
* The identity clamps MUST match bulkUpsert's, or this mirror
|
|
494
|
+
* UPDATE would silently miss the row the upsert just wrote
|
|
495
|
+
* under the truncated name. controllerDeploymentName /
|
|
496
|
+
* controllerCronJobName are assigned columns, so an oversized
|
|
497
|
+
* value there aborts the chunk exactly like the INSERT path.
|
|
498
|
+
*/
|
|
453
499
|
params.push(
|
|
454
|
-
m.kind,
|
|
455
|
-
m.namespaceKey,
|
|
456
|
-
m.name,
|
|
500
|
+
truncateShortText(m.kind),
|
|
501
|
+
truncateShortText(m.namespaceKey),
|
|
502
|
+
truncateLongText(m.name),
|
|
457
503
|
m.cpuPercent !== null && m.cpuPercent !== undefined
|
|
458
504
|
? m.cpuPercent
|
|
459
505
|
: null,
|
|
@@ -464,8 +510,8 @@ export class Service extends DatabaseService<Model> {
|
|
|
464
510
|
? m.memoryPercent
|
|
465
511
|
: null,
|
|
466
512
|
m.observedAt,
|
|
467
|
-
m.controllerDeploymentName ?? null,
|
|
468
|
-
m.controllerCronJobName ?? null,
|
|
513
|
+
truncateLongText(m.controllerDeploymentName ?? null),
|
|
514
|
+
truncateShortText(m.controllerCronJobName ?? null),
|
|
469
515
|
);
|
|
470
516
|
}
|
|
471
517
|
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import CreateBy from "../Types/Database/CreateBy";
|
|
2
|
-
import
|
|
2
|
+
import DeleteBy from "../Types/Database/DeleteBy";
|
|
3
|
+
import { OnCreate, OnDelete } from "../Types/Database/Hooks";
|
|
3
4
|
import QueryHelper from "../Types/Database/QueryHelper";
|
|
4
5
|
import DatabaseService from "./DatabaseService";
|
|
6
|
+
import ServiceLevelObjectiveMonitorRuleEngineService from "./ServiceLevelObjectiveMonitorRuleEngineService";
|
|
7
|
+
import ServiceLevelObjectiveService from "./ServiceLevelObjectiveService";
|
|
8
|
+
import ServiceLevelObjective from "../../Models/DatabaseModels/ServiceLevelObjective";
|
|
5
9
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
10
|
+
import LIMIT_MAX from "../../Types/Database/LimitMax";
|
|
6
11
|
import ObjectID from "../../Types/ObjectID";
|
|
7
12
|
import Color from "../../Types/Color";
|
|
8
13
|
import { BrightColors } from "../../Types/BrandColors";
|
|
9
14
|
import GlobalCache from "../Infrastructure/GlobalCache";
|
|
10
15
|
import Model from "../../Models/DatabaseModels/Label";
|
|
11
16
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
17
|
+
import logger from "../Utils/Logger";
|
|
12
18
|
|
|
13
19
|
const AUTO_LABEL_ID_CACHE_NAMESPACE: string = "auto-label-id";
|
|
14
20
|
const AUTO_LABEL_ID_CACHE_TTL_SECONDS: number = 24 * 60 * 60;
|
|
@@ -57,6 +63,105 @@ export class Service extends DatabaseService<Model> {
|
|
|
57
63
|
return Promise.resolve({ createBy, carryForward: null });
|
|
58
64
|
}
|
|
59
65
|
|
|
66
|
+
/*
|
|
67
|
+
* Deleting a label cascades its join rows away at the database level, so no
|
|
68
|
+
* service hook ever fires for the SLO label rules that referenced it. Those
|
|
69
|
+
* SLOs would keep the monitors the rule attached, with nothing left to
|
|
70
|
+
* explain why. Note the ids down while the label still exists, and re-run
|
|
71
|
+
* the (now smaller) rules once it is gone.
|
|
72
|
+
*/
|
|
73
|
+
@CaptureSpan()
|
|
74
|
+
protected override async onBeforeDelete(
|
|
75
|
+
deleteBy: DeleteBy<Model>,
|
|
76
|
+
): Promise<OnDelete<Model>> {
|
|
77
|
+
let serviceLevelObjectiveIds: Array<ObjectID> = [];
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const labels: Array<Model> = await this.findBy({
|
|
81
|
+
query: deleteBy.query,
|
|
82
|
+
select: {
|
|
83
|
+
_id: true,
|
|
84
|
+
},
|
|
85
|
+
limit: LIMIT_MAX,
|
|
86
|
+
skip: 0,
|
|
87
|
+
props: {
|
|
88
|
+
isRoot: true,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const labelIds: Array<ObjectID> = labels
|
|
93
|
+
.map((label: Model) => {
|
|
94
|
+
return label.id;
|
|
95
|
+
})
|
|
96
|
+
.filter((id: ObjectID | null): id is ObjectID => {
|
|
97
|
+
return Boolean(id);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (labelIds.length > 0) {
|
|
101
|
+
const slos: Array<ServiceLevelObjective> =
|
|
102
|
+
await ServiceLevelObjectiveService.findBy({
|
|
103
|
+
query: {
|
|
104
|
+
monitorLabels: labelIds,
|
|
105
|
+
},
|
|
106
|
+
select: {
|
|
107
|
+
_id: true,
|
|
108
|
+
},
|
|
109
|
+
limit: LIMIT_MAX,
|
|
110
|
+
skip: 0,
|
|
111
|
+
props: {
|
|
112
|
+
isRoot: true,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
serviceLevelObjectiveIds = slos
|
|
117
|
+
.map((slo: ServiceLevelObjective) => {
|
|
118
|
+
return slo.id;
|
|
119
|
+
})
|
|
120
|
+
.filter((id: ObjectID | null): id is ObjectID => {
|
|
121
|
+
return Boolean(id);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
logger.error(
|
|
126
|
+
`Error collecting SLO label rules affected by a label delete: ${err}`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
deleteBy,
|
|
132
|
+
carryForward: {
|
|
133
|
+
serviceLevelObjectiveIds: serviceLevelObjectiveIds,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@CaptureSpan()
|
|
139
|
+
protected override async onDeleteSuccess(
|
|
140
|
+
onDelete: OnDelete<Model>,
|
|
141
|
+
_itemIdsBeforeDelete: Array<ObjectID>,
|
|
142
|
+
): Promise<OnDelete<Model>> {
|
|
143
|
+
const serviceLevelObjectiveIds: Array<ObjectID> =
|
|
144
|
+
(
|
|
145
|
+
onDelete.carryForward as {
|
|
146
|
+
serviceLevelObjectiveIds?: Array<ObjectID>;
|
|
147
|
+
} | null
|
|
148
|
+
)?.serviceLevelObjectiveIds || [];
|
|
149
|
+
|
|
150
|
+
for (const serviceLevelObjectiveId of serviceLevelObjectiveIds) {
|
|
151
|
+
try {
|
|
152
|
+
await ServiceLevelObjectiveMonitorRuleEngineService.syncMonitorsForSlo({
|
|
153
|
+
serviceLevelObjectiveId: serviceLevelObjectiveId,
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
logger.error(
|
|
157
|
+
`Error re-applying the monitor label rule for SLO ${serviceLevelObjectiveId.toString()} after a label delete: ${err}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return onDelete;
|
|
163
|
+
}
|
|
164
|
+
|
|
60
165
|
/**
|
|
61
166
|
* Find existing labels by name (case-insensitive) or auto-create
|
|
62
167
|
* them, returning the ObjectIDs in the same order as the input.
|
|
@@ -14,6 +14,7 @@ import MonitorOwnerTeamService from "./MonitorOwnerTeamService";
|
|
|
14
14
|
import MonitorOwnerUserService from "./MonitorOwnerUserService";
|
|
15
15
|
import MonitorProbeService from "./MonitorProbeService";
|
|
16
16
|
import MonitorStatusService from "./MonitorStatusService";
|
|
17
|
+
import ServiceLevelObjectiveMonitorRuleEngineService from "./ServiceLevelObjectiveMonitorRuleEngineService";
|
|
17
18
|
import NetworkSiteService from "./NetworkSiteService";
|
|
18
19
|
import MonitorStatusTimelineService, {
|
|
19
20
|
MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
|
|
@@ -612,6 +613,37 @@ export class Service extends DatabaseService<Model> {
|
|
|
612
613
|
}
|
|
613
614
|
}
|
|
614
615
|
|
|
616
|
+
/*
|
|
617
|
+
* Labels decide SLO membership, so a label added or removed here can pull
|
|
618
|
+
* this monitor into an SLO's error budget or push it out of one. Keyed on
|
|
619
|
+
* `!== undefined` rather than on a non-empty array: clearing every label
|
|
620
|
+
* arrives as `[]`, and that is precisely the edit that should detach the
|
|
621
|
+
* monitor from every rule-driven SLO.
|
|
622
|
+
*/
|
|
623
|
+
if (
|
|
624
|
+
onUpdate.updateBy.data.labels !== undefined &&
|
|
625
|
+
updatedItemIds.length > 0
|
|
626
|
+
) {
|
|
627
|
+
for (const monitorId of updatedItemIds) {
|
|
628
|
+
try {
|
|
629
|
+
await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor(
|
|
630
|
+
{
|
|
631
|
+
monitorId: monitorId,
|
|
632
|
+
projectId: onUpdate.updateBy.props.tenantId as ObjectID,
|
|
633
|
+
},
|
|
634
|
+
);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
logger.error(
|
|
637
|
+
"Syncing SLO label rules failed in MonitorService.onUpdateSuccess",
|
|
638
|
+
{
|
|
639
|
+
monitorId: monitorId?.toString(),
|
|
640
|
+
} as LogAttributes,
|
|
641
|
+
);
|
|
642
|
+
logger.error(error as Error);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
615
647
|
return onUpdate;
|
|
616
648
|
}
|
|
617
649
|
|
|
@@ -983,6 +1015,31 @@ ${createdItem.description?.trim() || "No description provided."}
|
|
|
983
1015
|
}
|
|
984
1016
|
return Promise.resolve();
|
|
985
1017
|
})
|
|
1018
|
+
.then(async () => {
|
|
1019
|
+
/*
|
|
1020
|
+
* Runs after the label rules above so a monitor created with no labels
|
|
1021
|
+
* of its own, but given some by a MonitorLabelRule, still lands in the
|
|
1022
|
+
* SLOs those labels imply.
|
|
1023
|
+
*/
|
|
1024
|
+
try {
|
|
1025
|
+
await ServiceLevelObjectiveMonitorRuleEngineService.syncSlosForMonitor(
|
|
1026
|
+
{
|
|
1027
|
+
monitorId: createdItem.id!,
|
|
1028
|
+
projectId: createdItem.projectId!,
|
|
1029
|
+
},
|
|
1030
|
+
);
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
logger.error(
|
|
1033
|
+
"Syncing SLO label rules failed in MonitorService.onCreateSuccess",
|
|
1034
|
+
{
|
|
1035
|
+
projectId: createdItem.projectId?.toString(),
|
|
1036
|
+
monitorId: createdItem.id?.toString(),
|
|
1037
|
+
} as LogAttributes,
|
|
1038
|
+
);
|
|
1039
|
+
logger.error(error as Error);
|
|
1040
|
+
}
|
|
1041
|
+
return Promise.resolve();
|
|
1042
|
+
})
|
|
986
1043
|
.then(async () => {
|
|
987
1044
|
try {
|
|
988
1045
|
return await this.refreshMonitorProbeStatus(createdItem.id!);
|