Skip to content

Commit 5fe1c05

Browse files
committed
allow leader election to accept namespace
* remove isRunLocal() * change errNoNamespace value, it now returns "Namespace is required when running outside the cluster" * update tests
1 parent 551d499 commit 5fe1c05

File tree

2 files changed

+118
-26
lines changed

2 files changed

+118
-26
lines changed

leader/leader.go

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,10 @@ var forceRunModeEnv = "OSDK_FORCE_RUN_MODE"
4444

4545
// errNoNamespace indicates that a namespace could not be found for the current
4646
// environment
47-
var errNoNamespace = fmt.Errorf("namespace not found for current environment")
47+
var errNoNamespace = fmt.Errorf("Namespace is required when running outside the cluster")
4848

49-
// errRunLocal indicates that the operator is set to run in local mode (this error
50-
// is returned by functions that only work on operators running in cluster mode)
51-
var errRunLocal = fmt.Errorf("operator run mode forced to local")
49+
// namespaceDir is the default location where namespace information is stored
50+
var namespaceDir = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
5251

5352
// podNameEnvVar is the constant for env variable POD_NAME
5453
// which is the name of the current pod.
@@ -60,23 +59,22 @@ var log = logf.Log.WithName("leader")
6059
// attempts to become the leader.
6160
const maxBackoffInterval = time.Second * 16
6261

63-
// Become ensures that the current pod is the leader within its namespace. If
64-
// run outside a cluster, it will skip leader election and return nil. It
62+
// Become ensures that the current pod is the leader within the given
63+
// namespace. If run outside a cluster, it will return an error. It
6564
// continuously tries to create a ConfigMap with the provided name and the
6665
// current pod set as the owner reference. Only one can exist at a time with
6766
// the same name, so the pod that successfully creates the ConfigMap is the
68-
// leader. Upon termination of that pod, the garbage collector will delete the
69-
// ConfigMap, enabling a different pod to become the leader.
70-
func Become(ctx context.Context, lockName string) error {
67+
// leader. Upon termination of that pod, the garbage collector will delete
68+
// the ConfigMap, enabling a different pod to become the leader.
69+
func Become(ctx context.Context, lockName string, ns string) error {
7170
log.Info("Trying to become the leader.")
7271

73-
ns, err := getOperatorNamespace()
74-
if err != nil {
75-
if err == errNoNamespace || err == errRunLocal {
76-
log.Info("Skipping leader election; not running in a cluster.")
77-
return nil
72+
if ns == "" {
73+
namespace, err := getOperatorNamespace()
74+
if err != nil {
75+
return err
7876
}
79-
return err
77+
ns = namespace
8078
}
8179

8280
config, err := config.GetConfig()
@@ -211,10 +209,7 @@ func isPodEvicted(pod corev1.Pod) bool {
211209

212210
// getOperatorNamespace returns the namespace the operator should be running in.
213211
func getOperatorNamespace() (string, error) {
214-
if isRunModeLocal() {
215-
return "", errRunLocal
216-
}
217-
nsBytes, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
212+
nsBytes, err := ioutil.ReadFile(namespaceDir)
218213
if err != nil {
219214
if os.IsNotExist(err) {
220215
return "", errNoNamespace
@@ -226,17 +221,10 @@ func getOperatorNamespace() (string, error) {
226221
return ns, nil
227222
}
228223

229-
func isRunModeLocal() bool {
230-
return os.Getenv(forceRunModeEnv) == string(localRunMode)
231-
}
232-
233224
// getPod returns a Pod object that corresponds to the pod in which the code
234225
// is currently running.
235226
// It expects the environment variable POD_NAME to be set by the downwards API.
236227
func getPod(ctx context.Context, client crclient.Client, ns string) (*corev1.Pod, error) {
237-
if isRunModeLocal() {
238-
return nil, errRunLocal
239-
}
240228
podName := os.Getenv(podNameEnvVar)
241229
if podName == "" {
242230
return nil, fmt.Errorf("required env %s not set, please configure downward API", podNameEnvVar)

leader/leader_test.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package leader
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
corev1 "k8s.io/api/core/v1"
8+
)
9+
10+
func TestBecome(t *testing.T) {
11+
err := Become(context.TODO(), "testOperator", "foobar")
12+
if err == nil {
13+
t.Fatal("expected it to fail")
14+
}
15+
}
16+
17+
func TestGetOperatorNamespace(t *testing.T) {
18+
namespaceDir = "/tmp/namespace"
19+
20+
testCases := []struct {
21+
name string
22+
expected string
23+
expectedErr bool
24+
}{
25+
{
26+
name: "no namespace available",
27+
expectedErr: true,
28+
},
29+
}
30+
31+
// test
32+
for _, tc := range testCases {
33+
t.Run(tc.name, func(t *testing.T) {
34+
actual, err := getOperatorNamespace()
35+
if tc.expectedErr {
36+
if err == nil {
37+
t.Fatal("expected error but got none")
38+
}
39+
}
40+
if actual != tc.expected {
41+
t.Fatalf("expected %v received %v", tc.expected, actual)
42+
}
43+
})
44+
}
45+
}
46+
47+
func TestIsPodEvicted(t *testing.T) {
48+
testCases := []struct {
49+
name string
50+
pod corev1.Pod
51+
expected bool
52+
}{
53+
{
54+
name: "Evicted pod returns true",
55+
expected: true,
56+
pod: corev1.Pod{
57+
Status: corev1.PodStatus{
58+
Phase: corev1.PodFailed,
59+
Reason: "Evicted",
60+
},
61+
},
62+
},
63+
{
64+
name: "Failed pod but not evicted returns false",
65+
expected: false,
66+
pod: corev1.Pod{
67+
Status: corev1.PodStatus{
68+
Phase: corev1.PodFailed,
69+
Reason: "Don't know",
70+
},
71+
},
72+
},
73+
{
74+
name: "Succeded pod returns false",
75+
expected: false,
76+
pod: corev1.Pod{
77+
Status: corev1.PodStatus{
78+
Phase: corev1.PodSucceeded,
79+
},
80+
},
81+
},
82+
{
83+
name: "Invalid reason for pod returns false",
84+
expected: false,
85+
pod: corev1.Pod{
86+
Status: corev1.PodStatus{
87+
Phase: corev1.PodSucceeded,
88+
Reason: "Evicted",
89+
},
90+
},
91+
},
92+
}
93+
94+
// test
95+
for _, tc := range testCases {
96+
t.Run(tc.name, func(t *testing.T) {
97+
actual := isPodEvicted(tc.pod)
98+
if actual != tc.expected {
99+
t.Fatalf("expected %v received %v", tc.expected, actual)
100+
}
101+
})
102+
}
103+
104+
}

0 commit comments

Comments
 (0)