sigs.k8s.io/cluster-api@v1.7.1/bootstrap/kubeadm/internal/locking/control_plane_init_mutex_test.go (about)

     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 locking
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"testing"
    24  
    25  	. "github.com/onsi/gomega"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	ctrl "sigs.k8s.io/controller-runtime"
    33  	"sigs.k8s.io/controller-runtime/pkg/client"
    34  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    35  
    36  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    37  )
    38  
    39  const (
    40  	clusterName      = "test-cluster"
    41  	clusterNamespace = "test-namespace"
    42  )
    43  
    44  var (
    45  	ctx = ctrl.SetupSignalHandler()
    46  )
    47  
    48  func TestControlPlaneInitMutex_Lock(t *testing.T) {
    49  	g := NewWithT(t)
    50  
    51  	scheme := runtime.NewScheme()
    52  	g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
    53  	g.Expect(corev1.AddToScheme(scheme)).To(Succeed())
    54  
    55  	uid := types.UID("test-uid")
    56  	tests := []struct {
    57  		name          string
    58  		client        client.Client
    59  		shouldAcquire bool
    60  	}{
    61  		{
    62  			name: "should successfully acquire lock if the config cannot be found",
    63  			client: &fakeClient{
    64  				Client:   fake.NewClientBuilder().WithScheme(scheme).Build(),
    65  				getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
    66  			},
    67  			shouldAcquire: true,
    68  		},
    69  		{
    70  			name: "should not acquire lock if already exits",
    71  			client: &fakeClient{
    72  				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(&corev1.ConfigMap{
    73  					ObjectMeta: metav1.ObjectMeta{
    74  						Name:      configMapName(clusterName),
    75  						Namespace: clusterNamespace,
    76  					},
    77  				}).Build(),
    78  			},
    79  			shouldAcquire: false,
    80  		},
    81  		{
    82  			name: "should not acquire lock if cannot create config map",
    83  			client: &fakeClient{
    84  				Client:      fake.NewClientBuilder().WithScheme(scheme).Build(),
    85  				getError:    apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, configMapName(clusterName)),
    86  				createError: errors.New("create error"),
    87  			},
    88  			shouldAcquire: false,
    89  		},
    90  		{
    91  			name: "should not acquire lock if config map already exists while creating",
    92  			client: &fakeClient{
    93  				Client:      fake.NewClientBuilder().WithScheme(scheme).Build(),
    94  				getError:    apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
    95  				createError: apierrors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
    96  			},
    97  			shouldAcquire: false,
    98  		},
    99  	}
   100  
   101  	for _, tc := range tests {
   102  		tc := tc
   103  		t.Run(tc.name, func(t *testing.T) {
   104  			gs := NewWithT(t)
   105  
   106  			l := &ControlPlaneInitMutex{
   107  				client: tc.client,
   108  			}
   109  
   110  			cluster := &clusterv1.Cluster{
   111  				ObjectMeta: metav1.ObjectMeta{
   112  					Namespace: clusterNamespace,
   113  					Name:      clusterName,
   114  					UID:       uid,
   115  				},
   116  			}
   117  			machine := &clusterv1.Machine{
   118  				ObjectMeta: metav1.ObjectMeta{
   119  					Name: fmt.Sprintf("machine-%s", cluster.Name),
   120  				},
   121  			}
   122  
   123  			gs.Expect(l.Lock(ctx, cluster, machine)).To(Equal(tc.shouldAcquire))
   124  		})
   125  	}
   126  }
   127  
   128  func TestControlPlaneInitMutex_LockWithMachineDeletion(t *testing.T) {
   129  	g := NewWithT(t)
   130  
   131  	scheme := runtime.NewScheme()
   132  	g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
   133  	g.Expect(corev1.AddToScheme(scheme)).To(Succeed())
   134  
   135  	newMachineName := "new-machine"
   136  	tests := []struct {
   137  		name                string
   138  		client              client.Client
   139  		expectedMachineName string
   140  	}{
   141  		{
   142  			name: "should not give the lock to new machine if the machine that created it does exist",
   143  			client: &fakeClient{
   144  				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(
   145  					&corev1.ConfigMap{
   146  						ObjectMeta: metav1.ObjectMeta{
   147  							Name:      configMapName(clusterName),
   148  							Namespace: clusterNamespace},
   149  						Data: map[string]string{
   150  							"lock-information": "{\"machineName\":\"existent-machine\"}",
   151  						}},
   152  					&clusterv1.Machine{
   153  						ObjectMeta: metav1.ObjectMeta{
   154  							Name:      "existent-machine",
   155  							Namespace: clusterNamespace,
   156  						},
   157  					},
   158  				).Build(),
   159  			},
   160  			expectedMachineName: "existent-machine",
   161  		},
   162  		{
   163  			name: "should give the lock to new machine if the machine that created it does not exist",
   164  			client: &fakeClient{
   165  				Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(
   166  					&corev1.ConfigMap{
   167  						ObjectMeta: metav1.ObjectMeta{
   168  							Name:      configMapName(clusterName),
   169  							Namespace: clusterNamespace},
   170  						Data: map[string]string{
   171  							"lock-information": "{\"machineName\":\"non-existent-machine\"}",
   172  						}},
   173  				).Build(),
   174  			},
   175  			expectedMachineName: newMachineName,
   176  		},
   177  	}
   178  	for _, tc := range tests {
   179  		t.Run(tc.name, func(*testing.T) {
   180  			l := &ControlPlaneInitMutex{
   181  				client: tc.client,
   182  			}
   183  
   184  			cluster := &clusterv1.Cluster{
   185  				ObjectMeta: metav1.ObjectMeta{
   186  					Namespace: clusterNamespace,
   187  					Name:      clusterName,
   188  				},
   189  			}
   190  			machine := &clusterv1.Machine{
   191  				ObjectMeta: metav1.ObjectMeta{
   192  					Name: newMachineName,
   193  				},
   194  			}
   195  
   196  			g.Eventually(func(g Gomega) error {
   197  				l.Lock(ctx, cluster, machine)
   198  
   199  				cm := &corev1.ConfigMap{}
   200  				g.Expect(tc.client.Get(ctx, client.ObjectKey{
   201  					Name:      configMapName(clusterName),
   202  					Namespace: cluster.Namespace,
   203  				}, cm)).To(Succeed())
   204  
   205  				info, err := semaphore{cm}.information()
   206  				g.Expect(err).ToNot(HaveOccurred())
   207  
   208  				g.Expect(info.MachineName).To(Equal(tc.expectedMachineName))
   209  				return nil
   210  			}, "20s").Should(Succeed())
   211  		})
   212  	}
   213  }
   214  
   215  func TestControlPlaneInitMutex_UnLock(t *testing.T) {
   216  	uid := types.UID("test-uid")
   217  	configMap := &corev1.ConfigMap{
   218  		ObjectMeta: metav1.ObjectMeta{
   219  			Name:      configMapName(clusterName),
   220  			Namespace: clusterNamespace,
   221  		},
   222  	}
   223  	tests := []struct {
   224  		name          string
   225  		client        client.Client
   226  		shouldRelease bool
   227  	}{
   228  		{
   229  			name: "should release lock by deleting config map",
   230  			client: &fakeClient{
   231  				Client: fake.NewClientBuilder().Build(),
   232  			},
   233  			shouldRelease: true,
   234  		},
   235  		{
   236  			name: "should not release lock if cannot delete config map",
   237  			client: &fakeClient{
   238  				Client:      fake.NewClientBuilder().WithObjects(configMap).Build(),
   239  				deleteError: errors.New("delete error"),
   240  			},
   241  			shouldRelease: false,
   242  		},
   243  		{
   244  			name: "should release lock if config map does not exist",
   245  			client: &fakeClient{
   246  				Client:   fake.NewClientBuilder().Build(),
   247  				getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
   248  			},
   249  			shouldRelease: true,
   250  		},
   251  		{
   252  			name: "should not release lock if error while getting config map",
   253  			client: &fakeClient{
   254  				Client:   fake.NewClientBuilder().Build(),
   255  				getError: errors.New("get error"),
   256  			},
   257  			shouldRelease: false,
   258  		},
   259  	}
   260  
   261  	for _, tc := range tests {
   262  		tc := tc
   263  		t.Run(tc.name, func(t *testing.T) {
   264  			gs := NewWithT(t)
   265  
   266  			l := &ControlPlaneInitMutex{
   267  				client: tc.client,
   268  			}
   269  
   270  			cluster := &clusterv1.Cluster{
   271  				ObjectMeta: metav1.ObjectMeta{
   272  					Namespace: clusterNamespace,
   273  					Name:      clusterName,
   274  					UID:       uid,
   275  				},
   276  			}
   277  
   278  			gs.Expect(l.Unlock(ctx, cluster)).To(Equal(tc.shouldRelease))
   279  		})
   280  	}
   281  }
   282  
   283  type fakeClient struct {
   284  	client.Client
   285  	getError    error
   286  	createError error
   287  	deleteError error
   288  }
   289  
   290  func (fc *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
   291  	if fc.getError != nil {
   292  		return fc.getError
   293  	}
   294  	return fc.Client.Get(ctx, key, obj, opts...)
   295  }
   296  
   297  func (fc *fakeClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
   298  	if fc.createError != nil {
   299  		return fc.createError
   300  	}
   301  	return fc.Client.Create(ctx, obj, opts...)
   302  }
   303  
   304  func (fc *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
   305  	if fc.deleteError != nil {
   306  		return fc.deleteError
   307  	}
   308  	return fc.Client.Delete(ctx, obj, opts...)
   309  }