master
go 126 lines 3.59 KB
Raw
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 }