Skip to content

Commit 84c8997

Browse files
committed
feat: add environment variables for leader election timing configuration
Add support for configuring leader election timing parameters through environment variables. Changes: - Add environment variable parsing in operator/main.go for: - KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION - KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE - KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD - Pass parsed durations to ctrl.NewManager Options - Add test in operator/main_test.go covering parsing, defaults and error handling - Add configuration docs All three environment variables are optional. When not set, controller-runtime uses Kubernetes defaults (LeaseDuration: 15s, RenewDeadline: 10s, RetryPeriod: 2s). This allows operators to adjust leader election timing for different cluster configurations and network conditions without requiring code changes. Fixes: #1331 Signed-off-by: Nader Ziada <[email protected]>
1 parent c343977 commit 84c8997

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

docs/operate.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,24 @@ Optional variables
7171
`OTEL_EXPORTER_OTLP_TRACES_TIMEOUT` - The batcher timeout in seconds to send batch of data points (`5` by default)
7272

7373
### Configuring Service Failover
74+
75+
# Configuring the KEDA HTTP Add-on Operator
76+
77+
## Leader Election Timing
78+
79+
When running multiple replicas of the operator for high availability, you can configure the leader election timing parameters:
80+
81+
- **`KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION`** - Duration that non-leader candidates will wait to force acquire leadership. Default: `15s` (Kubernetes default)
82+
- **`KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE`** - Duration that the acting leader will retry refreshing leadership before giving up. Default: `10s` (Kubernetes default)
83+
- **`KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD`** - Duration the LeaderElector clients should wait between tries of actions. Default: `2s` (Kubernetes default)
84+
85+
Example usage in deployment:
86+
```yaml
87+
env:
88+
- name: KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION
89+
value: "30s"
90+
- name: KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE
91+
value: "20s"
92+
- name: KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD
93+
value: "5s"
94+
```

operator/main.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import (
3434
httpv1alpha1 "github.com/kedacore/http-add-on/operator/apis/http/v1alpha1"
3535
httpcontrollers "github.com/kedacore/http-add-on/operator/controllers/http"
3636
"github.com/kedacore/http-add-on/operator/controllers/http/config"
37+
"github.com/kedacore/http-add-on/pkg/util"
3738
// +kubebuilder:scaffold:imports
3839
)
3940

@@ -86,6 +87,24 @@ func main() {
8687
os.Exit(1)
8788
}
8889

90+
leaseDuration, err := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION")
91+
if err != nil {
92+
setupLog.Error(err, "invalid KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION")
93+
os.Exit(1)
94+
}
95+
96+
renewDeadline, err := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE")
97+
if err != nil {
98+
setupLog.Error(err, "invalid KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE")
99+
os.Exit(1)
100+
}
101+
102+
retryPeriod, err := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD")
103+
if err != nil {
104+
setupLog.Error(err, "invalid KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD")
105+
os.Exit(1)
106+
}
107+
89108
var namespaces map[string]cache.Config
90109
if baseConfig.WatchNamespace != "" {
91110
namespaces = map[string]cache.Config{
@@ -103,6 +122,9 @@ func main() {
103122
LeaderElection: enableLeaderElection,
104123
LeaderElectionID: "http-add-on.keda.sh",
105124
LeaderElectionReleaseOnCancel: true,
125+
LeaseDuration: leaseDuration,
126+
RenewDeadline: renewDeadline,
127+
RetryPeriod: retryPeriod,
106128
Cache: cache.Options{
107129
DefaultNamespaces: namespaces,
108130
},

operator/main_test.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
Copyright 2025 The KEDA Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"testing"
21+
"time"
22+
23+
"github.com/kedacore/http-add-on/pkg/util"
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
func TestLeaderElectionEnvVarsIntegration(t *testing.T) {
28+
tests := []struct {
29+
name string
30+
envVars map[string]string
31+
expectedLease *time.Duration
32+
expectedRenew *time.Duration
33+
expectedRetry *time.Duration
34+
expectError bool
35+
}{
36+
{
37+
name: "all environment variables set with valid values",
38+
envVars: map[string]string{
39+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION": "30s",
40+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE": "20s",
41+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD": "5s",
42+
},
43+
expectedLease: durationPtr(30 * time.Second),
44+
expectedRenew: durationPtr(20 * time.Second),
45+
expectedRetry: durationPtr(5 * time.Second),
46+
expectError: false,
47+
},
48+
{
49+
name: "no environment variables set - should return nil for defaults",
50+
envVars: map[string]string{},
51+
expectedLease: nil,
52+
expectedRenew: nil,
53+
expectedRetry: nil,
54+
expectError: false,
55+
},
56+
{
57+
name: "invalid lease duration",
58+
envVars: map[string]string{
59+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION": "invalid",
60+
},
61+
expectError: true,
62+
},
63+
{
64+
name: "invalid renew deadline",
65+
envVars: map[string]string{
66+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE": "not-a-duration",
67+
},
68+
expectError: true,
69+
},
70+
{
71+
name: "invalid retry period",
72+
envVars: map[string]string{
73+
"KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD": "xyz",
74+
},
75+
expectError: true,
76+
},
77+
}
78+
79+
for _, tt := range tests {
80+
t.Run(tt.name, func(t *testing.T) {
81+
82+
for key, value := range tt.envVars {
83+
t.Setenv(key, value)
84+
}
85+
86+
leaseDuration, leaseErr := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION")
87+
renewDeadline, renewErr := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE")
88+
retryPeriod, retryErr := util.ResolveOsEnvDuration("KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD")
89+
90+
if tt.expectError {
91+
// At least one of the errors should be non-nil
92+
hasError := false
93+
if _, ok := tt.envVars["KEDA_HTTP_OPERATOR_LEADER_ELECTION_LEASE_DURATION"]; ok && leaseErr != nil {
94+
hasError = true
95+
}
96+
if _, ok := tt.envVars["KEDA_HTTP_OPERATOR_LEADER_ELECTION_RENEW_DEADLINE"]; ok && renewErr != nil {
97+
hasError = true
98+
}
99+
if _, ok := tt.envVars["KEDA_HTTP_OPERATOR_LEADER_ELECTION_RETRY_PERIOD"]; ok && retryErr != nil {
100+
hasError = true
101+
}
102+
if !hasError {
103+
t.Errorf("expected error but got none")
104+
}
105+
} else {
106+
// No errors expected
107+
if leaseErr != nil {
108+
t.Errorf("unexpected error for lease duration: %v", leaseErr)
109+
}
110+
if renewErr != nil {
111+
t.Errorf("unexpected error for renew deadline: %v", renewErr)
112+
}
113+
if retryErr != nil {
114+
t.Errorf("unexpected error for retry period: %v", retryErr)
115+
}
116+
117+
// Verify the parsed values match expectations
118+
assert.Equal(t, tt.expectedLease, leaseDuration)
119+
assert.Equal(t, tt.expectedRenew, renewDeadline)
120+
assert.Equal(t, tt.expectedRetry, retryPeriod)
121+
}
122+
})
123+
}
124+
}
125+
126+
func durationPtr(d time.Duration) *time.Duration {
127+
return &d
128+
}

0 commit comments

Comments
 (0)