Skip to content

Commit 2f43231

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 0ffd6fc commit 2f43231

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
@@ -43,11 +43,10 @@ var forceRunModeEnv = "OSDK_FORCE_RUN_MODE"
4343

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

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

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

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

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

8179
config, err := config.GetConfig()
@@ -210,10 +208,7 @@ func isPodEvicted(pod corev1.Pod) bool {
210208

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

228-
func isRunModeLocal() bool {
229-
return os.Getenv(forceRunModeEnv) == string(localRunMode)
230-
}
231-
232223
// getPod returns a Pod object that corresponds to the pod in which the code
233224
// is currently running.
234225
// It expects the environment variable POD_NAME to be set by the downwards API.
235226
func getPod(ctx context.Context, client crclient.Client, ns string) (*corev1.Pod, error) {
236-
if isRunModeLocal() {
237-
return nil, errRunLocal
238-
}
239227
podName := os.Getenv(podNameEnvVar)
240228
if podName == "" {
241229
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)