Skip to content
This repository was archived by the owner on Jul 30, 2021. It is now read-only.

Commit 1442654

Browse files
authored
Merge pull request #33 from SataQiu/add-init-lock
Move control plane locker from CAPA
2 parents d24f26c + 3f073f0 commit 1442654

File tree

2 files changed

+330
-0
lines changed

2 files changed

+330
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
Copyright 2019 The Kubernetes 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 controllers
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/go-logr/logr"
23+
apicorev1 "k8s.io/api/core/v1"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
27+
clusterv2 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha2"
28+
)
29+
30+
// ControlPlaneInitLocker provides a locking mechanism for cluster initialization.
31+
type ControlPlaneInitLocker interface {
32+
// Acquire returns true if it acquires the lock for the cluster.
33+
Acquire(cluster *clusterv2.Cluster) bool
34+
}
35+
36+
// controlPlaneInitLocker uses a ConfigMap to synchronize cluster initialization.
37+
type controlPlaneInitLocker struct {
38+
log logr.Logger
39+
configMapClient corev1.ConfigMapsGetter
40+
}
41+
42+
var _ ControlPlaneInitLocker = &controlPlaneInitLocker{}
43+
44+
func newControlPlaneInitLocker(log logr.Logger, configMapClient corev1.ConfigMapsGetter) *controlPlaneInitLocker {
45+
return &controlPlaneInitLocker{
46+
log: log,
47+
configMapClient: configMapClient,
48+
}
49+
}
50+
51+
func (l *controlPlaneInitLocker) Acquire(cluster *clusterv2.Cluster) bool {
52+
configMapName := fmt.Sprintf("%s-controlplane", cluster.UID)
53+
log := l.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", configMapName)
54+
55+
exists, err := l.configMapExists(cluster.Namespace, configMapName)
56+
if err != nil {
57+
log.Error(err, "Error checking for control plane configmap lock existence")
58+
return false
59+
}
60+
if exists {
61+
return false
62+
}
63+
64+
controlPlaneConfigMap := &apicorev1.ConfigMap{
65+
ObjectMeta: metav1.ObjectMeta{
66+
Namespace: cluster.Namespace,
67+
Name: configMapName,
68+
OwnerReferences: []metav1.OwnerReference{
69+
{
70+
APIVersion: cluster.APIVersion,
71+
Kind: cluster.Kind,
72+
Name: cluster.Name,
73+
UID: cluster.UID,
74+
},
75+
},
76+
},
77+
}
78+
79+
log.Info("Attempting to create control plane configmap lock")
80+
_, err = l.configMapClient.ConfigMaps(cluster.Namespace).Create(controlPlaneConfigMap)
81+
if err != nil {
82+
if apierrors.IsAlreadyExists(err) {
83+
// Someone else beat us to it
84+
log.Info("Control plane configmap lock already exists")
85+
} else {
86+
log.Error(err, "Error creating control plane configmap lock")
87+
}
88+
89+
// Unable to acquire
90+
return false
91+
}
92+
93+
// Successfully acquired
94+
return true
95+
}
96+
97+
func (l *controlPlaneInitLocker) Release(cluster *clusterv2.Cluster) bool {
98+
configMapName := fmt.Sprintf("%s-controlplane", cluster.UID)
99+
log := l.log.WithValues("namespace", cluster.Namespace, "cluster-name", cluster.Name, "configmap-name", configMapName)
100+
101+
log.Info("Checking for existence of control plane configmap lock", "configmap-name", configMapName)
102+
_, err := l.configMapClient.ConfigMaps(cluster.Namespace).Get(configMapName, metav1.GetOptions{})
103+
switch {
104+
case apierrors.IsNotFound(err):
105+
log.Info("Control plane configmap lock not found, it may have been released already", "configmap-name", configMapName)
106+
case err != nil:
107+
log.Error(err, "Error retrieving control plane configmap lock", "configmap-name", configMapName)
108+
return false
109+
default:
110+
if err := l.configMapClient.ConfigMaps(cluster.Namespace).Delete(configMapName, nil); err != nil {
111+
log.Error(err, "Error deleting control plane configmap lock", "configmap-name", configMapName)
112+
return false
113+
}
114+
}
115+
// Successfully released
116+
return true
117+
}
118+
119+
func (l *controlPlaneInitLocker) configMapExists(namespace, name string) (bool, error) {
120+
_, err := l.configMapClient.ConfigMaps(namespace).Get(name, metav1.GetOptions{})
121+
if apierrors.IsNotFound(err) {
122+
return false, nil
123+
}
124+
125+
return err == nil, err
126+
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
Copyright 2019 The Kubernetes 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 controllers
18+
19+
import (
20+
"testing"
21+
22+
"github.com/pkg/errors"
23+
v1 "k8s.io/api/core/v1"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
25+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+
"k8s.io/apimachinery/pkg/runtime/schema"
27+
"k8s.io/apimachinery/pkg/types"
28+
"k8s.io/apimachinery/pkg/watch"
29+
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
30+
clusterv2 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha2"
31+
"sigs.k8s.io/controller-runtime/pkg/runtime/log"
32+
)
33+
34+
func TestControlPlaneInitLockerAcquire(t *testing.T) {
35+
tests := []struct {
36+
name string
37+
configMap *v1.ConfigMap
38+
getError error
39+
createError error
40+
expectAcquire bool
41+
}{
42+
{
43+
name: "configmap already exists",
44+
configMap: &v1.ConfigMap{},
45+
expectAcquire: false,
46+
},
47+
{
48+
name: "error getting configmap",
49+
getError: errors.New("get error"),
50+
expectAcquire: false,
51+
},
52+
{
53+
name: "create succeeds",
54+
getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, "uid1-configmap"),
55+
expectAcquire: true,
56+
},
57+
{
58+
name: "create fails",
59+
getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, "uid1-configmap"),
60+
createError: errors.New("create error"),
61+
expectAcquire: false,
62+
},
63+
}
64+
65+
for _, tc := range tests {
66+
t.Run(tc.name, func(t *testing.T) {
67+
l := &controlPlaneInitLocker{
68+
log: log.ZapLogger(true),
69+
configMapClient: &configMapsGetter{
70+
configMap: tc.configMap,
71+
getError: tc.getError,
72+
createError: tc.createError,
73+
},
74+
}
75+
76+
cluster := &clusterv2.Cluster{
77+
ObjectMeta: metav1.ObjectMeta{
78+
Namespace: "ns1",
79+
Name: "name1",
80+
UID: types.UID("uid1"),
81+
},
82+
}
83+
84+
acquired := l.Acquire(cluster)
85+
if tc.expectAcquire != acquired {
86+
t.Errorf("expected %t, got %t", tc.expectAcquire, acquired)
87+
}
88+
})
89+
}
90+
}
91+
92+
func TestControlPlaneInitLockerRelease(t *testing.T) {
93+
tests := []struct {
94+
name string
95+
configMap *v1.ConfigMap
96+
getError error
97+
deleteError error
98+
expectRelease bool
99+
}{
100+
{
101+
name: "error getting configmap",
102+
getError: errors.New("get error"),
103+
expectRelease: false,
104+
},
105+
{
106+
name: "configmap not found",
107+
getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, "uid1-configmap"),
108+
expectRelease: true,
109+
},
110+
{
111+
name: "delete succeeds",
112+
expectRelease: true,
113+
},
114+
{
115+
name: "delete fails",
116+
deleteError: errors.New("delete error"),
117+
expectRelease: false,
118+
},
119+
}
120+
121+
for _, tc := range tests {
122+
t.Run(tc.name, func(t *testing.T) {
123+
l := &controlPlaneInitLocker{
124+
log: log.ZapLogger(true),
125+
configMapClient: &configMapsGetter{
126+
configMap: tc.configMap,
127+
getError: tc.getError,
128+
deleteError: tc.deleteError,
129+
},
130+
}
131+
132+
cluster := &clusterv2.Cluster{
133+
ObjectMeta: metav1.ObjectMeta{
134+
Namespace: "ns1",
135+
Name: "name1",
136+
UID: types.UID("uid1"),
137+
},
138+
}
139+
140+
released := l.Release(cluster)
141+
if tc.expectRelease != released {
142+
t.Errorf("expected %t, got %t", tc.expectRelease, released)
143+
}
144+
})
145+
}
146+
}
147+
148+
type configMapsGetter struct {
149+
configMap *v1.ConfigMap
150+
getError error
151+
createError error
152+
deleteError error
153+
}
154+
155+
func (c *configMapsGetter) ConfigMaps(namespace string) corev1client.ConfigMapInterface {
156+
return &configMapClient{
157+
configMap: c.configMap,
158+
getError: c.getError,
159+
createError: c.createError,
160+
deleteError: c.deleteError,
161+
}
162+
}
163+
164+
type configMapClient struct {
165+
configMap *v1.ConfigMap
166+
getError error
167+
createError error
168+
deleteError error
169+
}
170+
171+
func (c *configMapClient) Create(configMap *v1.ConfigMap) (*v1.ConfigMap, error) {
172+
return c.configMap, c.createError
173+
}
174+
175+
func (c *configMapClient) Get(name string, getOptions metav1.GetOptions) (*v1.ConfigMap, error) {
176+
if c.getError != nil {
177+
return nil, c.getError
178+
}
179+
return c.configMap, nil
180+
}
181+
182+
func (c *configMapClient) Update(*v1.ConfigMap) (*v1.ConfigMap, error) {
183+
panic("not implemented")
184+
}
185+
186+
func (c *configMapClient) Delete(name string, options *metav1.DeleteOptions) error {
187+
return c.deleteError
188+
}
189+
190+
func (c *configMapClient) DeleteCollection(options *metav1.DeleteOptions, listOptions metav1.ListOptions) error {
191+
panic("not implemented")
192+
}
193+
194+
func (c *configMapClient) List(opts metav1.ListOptions) (*v1.ConfigMapList, error) {
195+
panic("not implemented")
196+
}
197+
198+
func (c *configMapClient) Watch(opts metav1.ListOptions) (watch.Interface, error) {
199+
panic("not implemented")
200+
}
201+
202+
func (c *configMapClient) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.ConfigMap, err error) {
203+
panic("not implemented")
204+
}

0 commit comments

Comments
 (0)