Skip to content

Commit 5e26c0e

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 d90bbc6 commit 5e26c0e

File tree

6 files changed

+122
-8
lines changed

6 files changed

+122
-8
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+
since edits to Leases are less common and fewer objects in the cluster watch "all Leases".
24+
If Lease API is not available then ConfigMaps resource lock is used.
25+
2326
*/
2427
package leaderelection

pkg/leaderelection/leader_election.go

Lines changed: 26 additions & 3 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"
@@ -45,8 +46,9 @@ type Options struct {
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 for use in a leader
50+
// election loop. Choose the Lease lock if `lease.coordination.k8s.io` is available
51+
// otherwise, ConfigMaps 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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package leaderelection
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"fmt"
7+
"io/ioutil"
8+
"net/http"
9+
10+
tlog "github.com/go-logr/logr/testing"
11+
. "github.com/onsi/ginkgo"
12+
. "github.com/onsi/gomega"
13+
coordinationv1 "k8s.io/api/coordination/v1"
14+
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
15+
"k8s.io/client-go/kubernetes/scheme"
16+
restclient "k8s.io/client-go/rest"
17+
"k8s.io/client-go/tools/leaderelection/resourcelock"
18+
"sigs.k8s.io/controller-runtime/pkg/internal/recorder"
19+
)
20+
21+
var _ = Describe("Leader Election", func() {
22+
It("should use the Lease lock because coordination group is available.", func() {
23+
coordinationGroup := &v1.APIGroupList{
24+
Groups: []v1.APIGroup{
25+
{Name: coordinationv1.GroupName},
26+
},
27+
}
28+
29+
clientConfig := &restclient.Config{
30+
Transport: interceptAPIGroupCall(coordinationGroup),
31+
}
32+
33+
rProvider, err := recorder.NewProvider(clientConfig, scheme.Scheme, tlog.NullLogger{})
34+
Expect(err).ToNot(HaveOccurred())
35+
36+
lock, err := NewResourceLock(clientConfig, rProvider, Options{LeaderElection: true, LeaderElectionNamespace: "test-ns"})
37+
Expect(err).ToNot(HaveOccurred())
38+
Expect(lock).To(BeAssignableToTypeOf(&resourcelock.LeaseLock{}))
39+
})
40+
41+
It("should use the ConfigMap lock because coordination group is unavailable.", func() {
42+
clientConfig := &restclient.Config{
43+
Transport: interceptAPIGroupCall(&v1.APIGroupList{ /* no coordination group */ }),
44+
}
45+
46+
rProvider, err := recorder.NewProvider(clientConfig, scheme.Scheme, tlog.NullLogger{})
47+
Expect(err).ToNot(HaveOccurred())
48+
49+
lock, err := NewResourceLock(clientConfig, rProvider, Options{LeaderElection: true, LeaderElectionNamespace: "test-ns"})
50+
Expect(err).ToNot(HaveOccurred())
51+
Expect(lock).To(BeAssignableToTypeOf(&resourcelock.ConfigMapLock{}))
52+
})
53+
})
54+
55+
func interceptAPIGroupCall(returnApis *v1.APIGroupList) roundTripper {
56+
return roundTripper(func(req *http.Request) (*http.Response, error) {
57+
if req.Method == "GET" && (req.URL.Path == "/apis" || req.URL.Path == "/api") {
58+
return encode(returnApis)
59+
}
60+
return nil, fmt.Errorf("unexpected request: %v %#v\n%#v", req.Method, req.URL, req)
61+
})
62+
}
63+
func encode(bodyStruct interface{}) (*http.Response, error) {
64+
jsonBytes, err := json.Marshal(bodyStruct)
65+
if err != nil {
66+
return nil, err
67+
}
68+
return &http.Response{
69+
StatusCode: http.StatusOK,
70+
Body: ioutil.NopCloser(bytes.NewReader(jsonBytes)),
71+
}, nil
72+
}
73+
74+
type roundTripper func(*http.Request) (*http.Response, error)
75+
76+
func (f roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
77+
return f(req)
78+
}

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)