| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | package snmp |
| 4 | |
| 5 | import ( |
| 6 | "strings" |
| 7 | "time" |
| 8 | ) |
| 9 | |
| 10 | type licenseStateBucket string |
| 11 | |
| 12 | const ( |
| 13 | licenseStateBucketHealthy licenseStateBucket = "healthy" |
| 14 | licenseStateBucketInformational licenseStateBucket = "informational" |
| 15 | licenseStateBucketDegraded licenseStateBucket = "degraded" |
| 16 | licenseStateBucketBroken licenseStateBucket = "broken" |
| 17 | licenseStateBucketIgnored licenseStateBucket = "ignored" |
| 18 | ) |
| 19 | |
| 20 | func normalizeLicenseStateBucket(row licenseRow, now time.Time) licenseStateBucket { |
| 21 | rawBucket, hasRawBucket := mapLicenseStateBucket(row.StateRaw) |
| 22 | if hasRawBucket && rawBucket == licenseStateBucketIgnored { |
| 23 | return licenseStateBucketIgnored |
| 24 | } |
| 25 | |
| 26 | // Hard-fail conditions: a broken-timer or fully-consumed pool is broken |
| 27 | // regardless of state. These come from FRESH metric values (the table |
| 28 | // cache re-fetches symbol PDUs on every poll), so they cannot be stale. |
| 29 | if licenseRowHasBrokenTimerOrUsage(row, now) { |
| 30 | return licenseStateBucketBroken |
| 31 | } |
| 32 | |
| 33 | if row.HasGraceTime { |
| 34 | return licenseStateBucketDegraded |
| 35 | } |
| 36 | |
| 37 | if hasRawBucket && rawBucket == licenseStateBucketInformational { |
| 38 | return licenseStateBucketInformational |
| 39 | } |
| 40 | |
| 41 | // Fresh severity wins over the cached raw state string. The raw state |
| 42 | // string lives in a same-table metric_tag and the SNMP table cache |
| 43 | // reuses row tags on cache hits, so a renewed-or-expired license could |
| 44 | // continue to read the previous text for up to the cache TTL. Severity, |
| 45 | // in contrast, is collected as a symbol whose value is re-fetched on |
| 46 | // every poll, so it is always current. |
| 47 | if row.HasState { |
| 48 | return bucketFromSeverity(row.StateSeverity) |
| 49 | } |
| 50 | |
| 51 | // No fresh severity → fall back to the raw vendor state classification. |
| 52 | if hasRawBucket { |
| 53 | return rawBucket |
| 54 | } |
| 55 | |
| 56 | if row.HasExpiry && !row.IsPerpetual { |
| 57 | return licenseStateBucketHealthy |
| 58 | } |
| 59 | if row.HasAuthorizationTime { |
| 60 | return licenseStateBucketHealthy |
| 61 | } |
| 62 | if row.HasCertificateTime { |
| 63 | return licenseStateBucketHealthy |
| 64 | } |
| 65 | if row.HasUsagePct || row.HasUsage || row.HasCapacity || row.HasAvailable { |
| 66 | return licenseStateBucketHealthy |
| 67 | } |
| 68 | if row.IsPerpetual || row.IsUnlimited { |
| 69 | return licenseStateBucketHealthy |
| 70 | } |
| 71 | if strings.TrimSpace(row.StateRaw) != "" { |
| 72 | return licenseStateBucketDegraded |
| 73 | } |
| 74 | return licenseStateBucketIgnored |
| 75 | } |
| 76 | |
| 77 | func licenseRowHasBrokenTimerOrUsage(row licenseRow, now time.Time) bool { |
| 78 | if row.HasGraceTime && row.GraceExpiry <= now.Unix() { |
| 79 | return true |
| 80 | } |
| 81 | if row.HasExpiry && !row.IsPerpetual && row.ExpiryTS <= now.Unix() { |
| 82 | return true |
| 83 | } |
| 84 | if row.HasAuthorizationTime && row.AuthorizationExpiry <= now.Unix() { |
| 85 | return true |
| 86 | } |
| 87 | if row.HasCertificateTime && row.CertificateExpiry <= now.Unix() { |
| 88 | return true |
| 89 | } |
| 90 | return !row.IsUnlimited && row.HasUsagePct && row.UsagePercent >= 100 |
| 91 | } |
| 92 | |
| 93 | func bucketFromSeverity(sev int64) licenseStateBucket { |
| 94 | switch sev { |
| 95 | case 2: |
| 96 | return licenseStateBucketBroken |
| 97 | case 1: |
| 98 | return licenseStateBucketDegraded |
| 99 | default: |
| 100 | return licenseStateBucketHealthy |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | func mapLicenseStateBucket(raw string) (licenseStateBucket, bool) { |
| 105 | if strings.TrimSpace(raw) == "" { |
| 106 | return "", false |
| 107 | } |
| 108 | |
| 109 | if licenseStateMatchesAny(raw, licenseStateIgnoredHints) { |
| 110 | return licenseStateBucketIgnored, true |
| 111 | } |
| 112 | if licenseStateMatchesAny(raw, licenseStateBrokenHints) { |
| 113 | return licenseStateBucketBroken, true |
| 114 | } |
| 115 | if licenseStateMatchesAny(raw, licenseStateInformationalHints) { |
| 116 | return licenseStateBucketInformational, true |
| 117 | } |
| 118 | if licenseStateMatchesAny(raw, licenseStateDegradedHints) { |
| 119 | return licenseStateBucketDegraded, true |
| 120 | } |
| 121 | if licenseStateMatchesAny(raw, licenseStateHealthyHints) { |
| 122 | return licenseStateBucketHealthy, true |
| 123 | } |
| 124 | |
| 125 | return "", false |
| 126 | } |