6
6
"io"
7
7
"net/http"
8
8
"net/url"
9
+ "sync"
9
10
"time"
10
11
11
12
configlistersv1 "github.com/openshift/client-go/config/listers/config/v1"
@@ -24,6 +25,17 @@ const (
24
25
TelemetryAnnotationPrefix = "telemetry.console.openshift.io/"
25
26
TelemeterClientDeploymentNamespace = "openshift-monitoring"
26
27
PullSecretName = "pull-secret"
28
+ // FetchInterval defines how often we can fetch from OCM after the last successful fetch
29
+ FetchInterval = 24 * time .Hour
30
+ // FailureBackoffInterval defines how often we retry after a failed or missing-data attempt
31
+ FailureBackoffInterval = 5 * time .Minute
32
+ )
33
+
34
+ var (
35
+ // Global timestamps for rate limiting
36
+ lastAttemptTime time.Time
37
+ lastSuccessTime time.Time
38
+ fetchMutex sync.RWMutex
27
39
)
28
40
29
41
func IsTelemeterClientAvailable (deploymentLister appsv1listers.DeploymentLister ) (bool , error ) {
@@ -93,24 +105,79 @@ func GetOrganizationMeta(telemetryConfig map[string]string, cachedOrganizationID
93
105
return customOrganizationID , customAccountMail , false
94
106
}
95
107
108
+ // If both cached values are available, prefer them without fetching
96
109
if cachedOrganizationID != "" && cachedAccountEmail != "" {
97
110
klog .V (4 ).Infoln ("telemetry config: using cached organization metadata" )
98
111
return cachedOrganizationID , cachedAccountEmail , false
99
112
}
100
113
101
- fetchedOCMRespose , err := FetchSubscription (clusterID , accessToken )
114
+ // Attempt rate-limited fetch of subscription
115
+ // We need to do this bacause in some cases the organization ID and account mail are not
116
+ // not available in the telemetry configmap, so we need to fetch it from OCM. But event there
117
+ // one of the value might not be available, so we need to check periodically.
118
+ fetchedOCMRespose , fetched , err := getSubscriptionWithRateLimit (clusterID , accessToken )
102
119
if err != nil {
103
120
klog .Errorf ("telemetry config error: %s" , err )
104
- return "" , "" , false // Ensure safe return in case of error
105
121
}
122
+ // If not fetched or error, proceed with cached values without clearing
123
+
124
+ // Merge per-field: only overwrite when the fetched value is non-empty
125
+ resolvedOrgID := cachedOrganizationID
126
+ if fetched && fetchedOCMRespose != nil && fetchedOCMRespose .Organization .ExternalId != "" {
127
+ resolvedOrgID = fetchedOCMRespose .Organization .ExternalId
128
+ }
129
+
130
+ resolvedAccountMail := cachedAccountEmail
131
+ if fetched && fetchedOCMRespose != nil && fetchedOCMRespose .Creator .Email != "" {
132
+ resolvedAccountMail = fetchedOCMRespose .Creator .Email
133
+ }
134
+
135
+ refresh := resolvedOrgID != cachedOrganizationID || resolvedAccountMail != cachedAccountEmail
136
+ return resolvedOrgID , resolvedAccountMail , refresh
137
+ }
138
+
139
+ // getSubscriptionWithRateLimit applies rate limiting using last attempt and last success timestamps
140
+ // and returns a subscription only if a fetch was performed and succeeded.
141
+ // fetched indicates whether a fetch was attempted and succeeded.
142
+ func getSubscriptionWithRateLimit (clusterID , accessToken string ) (* Subscription , bool , error ) {
143
+ // Check freshness windows
144
+ fetchMutex .RLock ()
145
+ successFresh := ! lastSuccessTime .IsZero () && time .Since (lastSuccessTime ) < FetchInterval
146
+ attemptFresh := ! lastAttemptTime .IsZero () && time .Since (lastAttemptTime ) < FailureBackoffInterval
147
+ fetchMutex .RUnlock ()
148
+
149
+ if successFresh || attemptFresh {
150
+ return nil , false , nil
151
+ }
152
+
153
+ // Mark attempt time, guarding against races
154
+ fetchMutex .Lock ()
155
+ if ! lastSuccessTime .IsZero () && time .Since (lastSuccessTime ) < FetchInterval {
156
+ fetchMutex .Unlock ()
157
+ return nil , false , nil
158
+ }
159
+ if ! lastAttemptTime .IsZero () && time .Since (lastAttemptTime ) < FailureBackoffInterval {
160
+ fetchMutex .Unlock ()
161
+ return nil , false , nil
162
+ }
163
+ lastAttemptTime = time .Now ()
164
+ fetchMutex .Unlock ()
106
165
107
- // Check if the fetched response is nil before accessing fields
108
- if fetchedOCMRespose == nil {
109
- klog . Errorf ( "telemetry config error: FetchSubscription returned nil response" )
110
- return "" , "" , false
166
+ // Perform fetch
167
+ subscription , err := FetchSubscription ( clusterID , accessToken )
168
+ if err != nil {
169
+ return nil , false , err
111
170
}
171
+ if subscription == nil {
172
+ return nil , false , fmt .Errorf ("nil subscription response" )
173
+ }
174
+
175
+ // Mark success time
176
+ fetchMutex .Lock ()
177
+ lastSuccessTime = time .Now ()
178
+ fetchMutex .Unlock ()
112
179
113
- return fetchedOCMRespose . Organization . ExternalId , fetchedOCMRespose . Creator . Email , true
180
+ return subscription , true , nil
114
181
}
115
182
116
183
// Needed to create our own types for OCM Subscriptions since their types and client are useless
0 commit comments