Skip to content

Commit 13fe693

Browse files
committed
Add option to choose Lease lock if coordination group is available
- Choose the Lease lock if lease.coordination.k8s.io is available otherwise, use ConfigMaps. - Add tests coverage for implemented logic using ginko & gomega - Remove outdated information about cert generation
1 parent 524b614 commit 13fe693

File tree

7 files changed

+127
-12
lines changed

7 files changed

+127
-12
lines changed

Gopkg.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/leaderelection/doc.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ Package leaderelection contains a constructors for a leader election resource lo
1919
This is used to ensure that multiple copies of a controller manager can be run with
2020
only one active set of controllers, for active-passive HA.
2121
22-
It uses built-in Kubernetes leader election APIs.
22+
It uses built-in Kubernetes leader election APIs. The Lease lock type takes precedence
23+
as edits to Leases are less common and fewer objects in the cluster watch "all Leases".
24+
If the Lease API is not available, the ConfigMap resource lock is used.
25+
2326
*/
2427
package leaderelection

pkg/leaderelection/leader_election.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"io/ioutil"
2222
"os"
2323

24+
coordinationv1 "k8s.io/api/coordination/v1"
2425
"k8s.io/apimachinery/pkg/util/uuid"
2526
"k8s.io/client-go/kubernetes"
2627
"k8s.io/client-go/rest"
@@ -37,16 +38,17 @@ type Options struct {
3738
LeaderElection bool
3839

3940
// LeaderElectionNamespace determines the namespace in which the leader
40-
// election configmap will be created.
41+
// election will be created.
4142
LeaderElectionNamespace string
4243

43-
// LeaderElectionID determines the name of the configmap that leader election
44+
// LeaderElectionID determines the name of the resource lock that leader election
4445
// will use for holding the leader lock.
4546
LeaderElectionID string
4647
}
4748

48-
// NewResourceLock creates a new config map resource lock for use in a leader
49-
// election loop
49+
// NewResourceLock creates a new resource lock to use in a leader
50+
// election loop. Choose the Lease lock if `lease.coordination.k8s.io` is available.
51+
// Otherwise, the ConfigMap resource lock is used.
5052
func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, options Options) (resourcelock.Interface, error) {
5153
if !options.LeaderElection {
5254
return nil, nil
@@ -79,8 +81,14 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
7981
return nil, err
8082
}
8183

84+
// Determine lock type
85+
lockType, err := getDefaultLockType(client)
86+
if err != nil {
87+
return nil, err
88+
}
89+
8290
// TODO(JoelSpeed): switch to leaderelection object in 1.12
83-
return resourcelock.New(resourcelock.ConfigMapsResourceLock,
91+
return resourcelock.New(lockType,
8492
options.LeaderElectionNamespace,
8593
options.LeaderElectionID,
8694
client.CoreV1(),
@@ -91,6 +99,21 @@ func NewResourceLock(config *rest.Config, recorderProvider recorder.Provider, op
9199
})
92100
}
93101

102+
func getDefaultLockType(client *kubernetes.Clientset) (string, error) {
103+
// check if new leader election api is available
104+
supportedGroups, err := client.Discovery().ServerGroups()
105+
if err != nil {
106+
return "", fmt.Errorf("unable to retrieve supported server groups: %v", err)
107+
}
108+
for _, g := range supportedGroups.Groups {
109+
if g.Name == coordinationv1.GroupName {
110+
return resourcelock.LeasesResourceLock, nil
111+
}
112+
}
113+
114+
return resourcelock.ConfigMapsResourceLock, nil
115+
}
116+
94117
func getInClusterNamespace() (string, error) {
95118
// Check whether the namespace file exists.
96119
// If not, we are not running in cluster so can't guess the namespace.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package leaderelection
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestLeaderElection(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Leader Election Suite")
13+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package leaderelection
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io/ioutil"
8+
"k8s.io/client-go/tools/record"
9+
"net/http"
10+
11+
tlog "github.com/go-logr/logr/testing"
12+
. "github.com/onsi/ginkgo"
13+
. "github.com/onsi/gomega"
14+
coordinationv1 "k8s.io/api/coordination/v1"
15+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
"k8s.io/client-go/kubernetes/scheme"
17+
restclient "k8s.io/client-go/rest"
18+
"k8s.io/client-go/tools/leaderelection/resourcelock"
19+
"sigs.k8s.io/controller-runtime/pkg/internal/recorder"
20+
)
21+
22+
var _ = Describe("Leader Election", func() {
23+
It("should use the Lease lock because coordination group is available.", func() {
24+
coordinationGroup := &v1.APIGroupList{
25+
Groups: []v1.APIGroup{
26+
{Name: coordinationv1.GroupName},
27+
},
28+
}
29+
30+
clientConfig := &restclient.Config{
31+
Transport: interceptAPIGroupCall(coordinationGroup),
32+
}
33+
34+
rProvider, err := recorder.NewProvider(clientConfig, scheme.Scheme, tlog.NullLogger{}, record.NewBroadcaster())
35+
Expect(err).ToNot(HaveOccurred())
36+
37+
lock, err := NewResourceLock(clientConfig, rProvider, Options{LeaderElection: true, LeaderElectionNamespace: "test-ns"})
38+
Expect(err).ToNot(HaveOccurred())
39+
Expect(lock).To(BeAssignableToTypeOf(&resourcelock.LeaseLock{}))
40+
})
41+
42+
It("should use the ConfigMap lock because coordination group is unavailable.", func() {
43+
clientConfig := &restclient.Config{
44+
Transport: interceptAPIGroupCall(&v1.APIGroupList{ /* no coordination group */ }),
45+
}
46+
47+
rProvider, err := recorder.NewProvider(clientConfig, scheme.Scheme, tlog.NullLogger{}, record.NewBroadcaster())
48+
Expect(err).ToNot(HaveOccurred())
49+
50+
lock, err := NewResourceLock(clientConfig, rProvider, Options{LeaderElection: true, LeaderElectionNamespace: "test-ns"})
51+
Expect(err).ToNot(HaveOccurred())
52+
Expect(lock).To(BeAssignableToTypeOf(&resourcelock.ConfigMapLock{}))
53+
})
54+
})
55+
56+
func interceptAPIGroupCall(returnApis *v1.APIGroupList) roundTripper {
57+
return roundTripper(func(req *http.Request) (*http.Response, error) {
58+
if req.Method == "GET" && (req.URL.Path == "/apis" || req.URL.Path == "/api") {
59+
return encode(returnApis)
60+
}
61+
return nil, fmt.Errorf("unexpected request: %v %#v\n%#v", req.Method, req.URL, req)
62+
})
63+
}
64+
func encode(bodyStruct interface{}) (*http.Response, error) {
65+
jsonBytes, err := json.Marshal(bodyStruct)
66+
if err != nil {
67+
return nil, err
68+
}
69+
return &http.Response{
70+
StatusCode: http.StatusOK,
71+
Body: ioutil.NopCloser(bytes.NewReader(jsonBytes)),
72+
}, nil
73+
}
74+
75+
type roundTripper func(*http.Request) (*http.Response, error)
76+
77+
func (f roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
78+
return f(req)
79+
}

pkg/manager/manager.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,10 @@ type Options struct {
111111
LeaderElection bool
112112

113113
// LeaderElectionNamespace determines the namespace in which the leader
114-
// election configmap will be created.
114+
// election will be created.
115115
LeaderElectionNamespace string
116116

117-
// LeaderElectionID determines the name of the configmap that leader election
117+
// LeaderElectionID determines the name of the resource lock that leader election
118118
// will use for holding the leader lock.
119119
LeaderElectionID string
120120

pkg/webhook/server.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,6 @@ type Server struct {
5353
Port int
5454

5555
// CertDir is the directory that contains the server key and certificate.
56-
// If using FSCertWriter in Provisioner, the server itself will provision the certificate and
57-
// store it in this directory.
58-
// If using SecretCertWriter in Provisioner, the server will provision the certificate in a secret,
59-
// the user is responsible to mount the secret to the this location for the server to consume.
6056
CertDir string
6157

6258
// WebhookMux is the multiplexer that handles different webhooks.

0 commit comments

Comments
 (0)