sigs.k8s.io/cluster-api/bootstrap/kubeadm@v0.0.0-20191016155141-23a891785b60/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  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"testing"
    25  
    26  	"github.com/go-logr/logr"
    27  	corev1 "k8s.io/api/core/v1"
    28  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/runtime"
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	"k8s.io/apimachinery/pkg/types"
    33  	"k8s.io/klog"
    34  	clusterv1 "sigs.k8s.io/cluster-api/api/v1alpha2"
    35  	"sigs.k8s.io/controller-runtime/pkg/client"
    36  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    37  	"sigs.k8s.io/controller-runtime/pkg/runtime/log"
    38  )
    39  
    40  const (
    41  	clusterName      = "test-cluster"
    42  	clusterNamespace = "test-namespace"
    43  )
    44  
    45  func init() {
    46  	klog.InitFlags(nil)
    47  }
    48  
    49  func TestControlPlaneInitMutex_Lock(t *testing.T) {
    50  	scheme := runtime.NewScheme()
    51  	if err := clusterv1.AddToScheme(scheme); err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	if err := corev1.AddToScheme(scheme); err != nil {
    55  		t.Fatal(err)
    56  	}
    57  	uid := types.UID("test-uid")
    58  
    59  	tests := []struct {
    60  		name          string
    61  		context       context.Context
    62  		client        client.Client
    63  		shouldAcquire bool
    64  	}{
    65  		{
    66  			name:    "should successfully acquire lock if the config cannot be found",
    67  			context: context.Background(),
    68  			client: &fakeClient{
    69  				Client:   fake.NewFakeClientWithScheme(scheme),
    70  				getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
    71  			},
    72  			shouldAcquire: true,
    73  		},
    74  		{
    75  			name:    "should not acquire lock if already exits",
    76  			context: context.Background(),
    77  			client: &fakeClient{
    78  				Client: fake.NewFakeClientWithScheme(scheme, &corev1.ConfigMap{
    79  					ObjectMeta: metav1.ObjectMeta{
    80  						Name:      configMapName(clusterName),
    81  						Namespace: clusterNamespace,
    82  					},
    83  				}),
    84  			},
    85  			shouldAcquire: false,
    86  		},
    87  		{
    88  			name:    "should not acquire lock if cannot create config map",
    89  			context: context.Background(),
    90  			client: &fakeClient{
    91  				Client:      fake.NewFakeClientWithScheme(scheme),
    92  				getError:    apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, configMapName(clusterName)),
    93  				createError: errors.New("create error"),
    94  			},
    95  			shouldAcquire: false,
    96  		},
    97  		{
    98  			name:    "should not acquire lock if config map already exists while creating",
    99  			context: context.Background(),
   100  			client: &fakeClient{
   101  				Client:      fake.NewFakeClientWithScheme(scheme),
   102  				getError:    apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
   103  				createError: apierrors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
   104  			},
   105  			shouldAcquire: false,
   106  		},
   107  	}
   108  
   109  	for _, tc := range tests {
   110  		tc := tc
   111  		t.Run(tc.name, func(t *testing.T) {
   112  			l := &ControlPlaneInitMutex{
   113  				log:    log.Log,
   114  				client: tc.client,
   115  			}
   116  
   117  			cluster := &clusterv1.Cluster{
   118  				ObjectMeta: metav1.ObjectMeta{
   119  					Namespace: clusterNamespace,
   120  					Name:      clusterName,
   121  					UID:       uid,
   122  				},
   123  			}
   124  			machine := &clusterv1.Machine{
   125  				ObjectMeta: metav1.ObjectMeta{
   126  					Name: fmt.Sprintf("machine-%s", cluster.Name),
   127  				},
   128  			}
   129  
   130  			actual := l.Lock(context.Background(), cluster, machine)
   131  			if actual != tc.shouldAcquire {
   132  				t.Fatalf("acquired was %v, but it should be %v", actual, tc.shouldAcquire)
   133  			}
   134  		})
   135  	}
   136  }
   137  func TestControlPlaneInitMutex_UnLock(t *testing.T) {
   138  	scheme := runtime.NewScheme()
   139  	if err := clusterv1.AddToScheme(scheme); err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	if err := corev1.AddToScheme(scheme); err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	uid := types.UID("test-uid")
   146  	configMap := &corev1.ConfigMap{
   147  		ObjectMeta: metav1.ObjectMeta{
   148  			Name:      configMapName(clusterName),
   149  			Namespace: clusterNamespace,
   150  		},
   151  	}
   152  	tests := []struct {
   153  		name          string
   154  		context       context.Context
   155  		client        client.Client
   156  		shouldRelease bool
   157  	}{
   158  		{
   159  			name:    "should release lock by deleting config map",
   160  			context: context.Background(),
   161  			client: &fakeClient{
   162  				Client: fake.NewFakeClientWithScheme(scheme),
   163  			},
   164  			shouldRelease: true,
   165  		},
   166  		{
   167  			name:    "should not release lock if cannot delete config map",
   168  			context: context.Background(),
   169  			client: &fakeClient{
   170  				Client:      fake.NewFakeClientWithScheme(scheme, configMap),
   171  				deleteError: errors.New("delete error"),
   172  			},
   173  			shouldRelease: false,
   174  		},
   175  		{
   176  			name:    "should release lock if config map does not exist",
   177  			context: context.Background(),
   178  			client: &fakeClient{
   179  				Client:   fake.NewFakeClientWithScheme(scheme),
   180  				getError: apierrors.NewNotFound(schema.GroupResource{Group: "", Resource: "configmaps"}, fmt.Sprintf("%s-controlplane", uid)),
   181  			},
   182  			shouldRelease: true,
   183  		},
   184  		{
   185  			name:    "should not release lock if error while getting config map",
   186  			context: context.Background(),
   187  			client: &fakeClient{
   188  				Client:   fake.NewFakeClientWithScheme(scheme),
   189  				getError: errors.New("get error"),
   190  			},
   191  			shouldRelease: false,
   192  		},
   193  	}
   194  
   195  	for _, tc := range tests {
   196  		tc := tc
   197  		t.Run(tc.name, func(t *testing.T) {
   198  			l := &ControlPlaneInitMutex{
   199  				log:    log.Log,
   200  				client: tc.client,
   201  			}
   202  
   203  			cluster := &clusterv1.Cluster{
   204  				ObjectMeta: metav1.ObjectMeta{
   205  					Namespace: clusterNamespace,
   206  					Name:      clusterName,
   207  					UID:       uid,
   208  				},
   209  			}
   210  
   211  			released := l.Unlock(context.Background(), cluster)
   212  			if released != tc.shouldRelease {
   213  				t.Fatalf("released was %v, but it should be %v\n", released, tc.shouldRelease)
   214  			}
   215  
   216  		})
   217  	}
   218  }
   219  
   220  func TestInfoLines_Lock(t *testing.T) {
   221  	scheme := runtime.NewScheme()
   222  	if err := clusterv1.AddToScheme(scheme); err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	if err := corev1.AddToScheme(scheme); err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	uid := types.UID("test-uid")
   229  	info := information{MachineName: "my-control-plane"}
   230  	b, err := json.Marshal(info)
   231  	if err != nil {
   232  		t.Fatal("failed to marshal info")
   233  	}
   234  	c := &fakeClient{
   235  		Client: fake.NewFakeClientWithScheme(scheme, &corev1.ConfigMap{
   236  			ObjectMeta: metav1.ObjectMeta{
   237  				Name:      configMapName(clusterName),
   238  				Namespace: clusterNamespace,
   239  			},
   240  			Data: map[string]string{semaphoreInformationKey: string(b)},
   241  		}),
   242  	}
   243  
   244  	logtester := &logtests{
   245  		InfoLog: make([]line, 0),
   246  	}
   247  	l := &ControlPlaneInitMutex{
   248  		log:    logtester,
   249  		client: c,
   250  	}
   251  
   252  	cluster := &clusterv1.Cluster{
   253  		ObjectMeta: metav1.ObjectMeta{
   254  			Namespace: clusterNamespace,
   255  			Name:      clusterName,
   256  			UID:       uid,
   257  		},
   258  	}
   259  	machine := &clusterv1.Machine{
   260  		ObjectMeta: metav1.ObjectMeta{
   261  			Name: fmt.Sprintf("machine-%s", cluster.Name),
   262  		},
   263  	}
   264  	if l.Lock(context.Background(), cluster, machine) != false {
   265  		t.Fatal("acquired lock but did not expect to")
   266  	}
   267  	foundLogLine := false
   268  	for _, line := range logtester.InfoLog {
   269  		fmt.Println(line)
   270  		for k, v := range line.data {
   271  			if k == "init-machine" && v.(string) == "my-control-plane" {
   272  				foundLogLine = true
   273  			}
   274  		}
   275  	}
   276  	if !foundLogLine {
   277  		t.Fatalf("Did not find the log line containing the name of the machine currently intializing")
   278  	}
   279  }
   280  
   281  type fakeClient struct {
   282  	client.Client
   283  	getError    error
   284  	createError error
   285  	deleteError error
   286  }
   287  
   288  func (fc *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj runtime.Object) error {
   289  	if fc.getError != nil {
   290  		return fc.getError
   291  	}
   292  	return fc.Client.Get(ctx, key, obj)
   293  }
   294  
   295  func (fc *fakeClient) Create(ctx context.Context, obj runtime.Object, opts ...client.CreateOption) error {
   296  	if fc.createError != nil {
   297  		return fc.createError
   298  	}
   299  	return fc.Client.Create(ctx, obj, opts...)
   300  }
   301  
   302  func (fc *fakeClient) Delete(ctx context.Context, obj runtime.Object, opts ...client.DeleteOption) error {
   303  	if fc.deleteError != nil {
   304  		return fc.deleteError
   305  	}
   306  	return fc.Client.Delete(ctx, obj, opts...)
   307  }
   308  
   309  type logtests struct {
   310  	logr.Logger
   311  	InfoLog []line
   312  }
   313  
   314  type line struct {
   315  	line string
   316  	data map[string]interface{}
   317  }
   318  
   319  func (l *logtests) Info(msg string, keysAndValues ...interface{}) {
   320  	data := make(map[string]interface{})
   321  	for i := 0; i < len(keysAndValues); i += 2 {
   322  		data[keysAndValues[i].(string)] = keysAndValues[i+1]
   323  	}
   324  	l.InfoLog = append(l.InfoLog, line{
   325  		line: msg,
   326  		data: data,
   327  	})
   328  }
   329  func (l *logtests) WithValues(keysAndValues ...interface{}) logr.Logger {
   330  	return l
   331  }