sigs.k8s.io/cluster-api@v1.7.1/internal/hooks/tracking_test.go (about)

     1  /*
     2  Copyright 2021 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 hooks
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"sigs.k8s.io/controller-runtime/pkg/client"
    27  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    28  
    29  	runtimev1 "sigs.k8s.io/cluster-api/exp/runtime/api/v1alpha1"
    30  	runtimecatalog "sigs.k8s.io/cluster-api/exp/runtime/catalog"
    31  	runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
    32  )
    33  
    34  func TestIsPending(t *testing.T) {
    35  	tests := []struct {
    36  		name string
    37  		obj  client.Object
    38  		hook runtimecatalog.Hook
    39  		want bool
    40  	}{
    41  		{
    42  			name: "should return true if the hook is marked as pending",
    43  			obj: &corev1.ConfigMap{
    44  				ObjectMeta: metav1.ObjectMeta{
    45  					Name:      "test-cluster",
    46  					Namespace: "test-ns",
    47  					Annotations: map[string]string{
    48  						runtimev1.PendingHooksAnnotation: "AfterClusterUpgrade",
    49  					},
    50  				},
    51  			},
    52  			hook: runtimehooksv1.AfterClusterUpgrade,
    53  			want: true,
    54  		},
    55  		{
    56  			name: "should return true if the hook is marked - other hooks are marked as pending too",
    57  			obj: &corev1.ConfigMap{
    58  				ObjectMeta: metav1.ObjectMeta{
    59  					Name:      "test-cluster",
    60  					Namespace: "test-ns",
    61  					Annotations: map[string]string{
    62  						runtimev1.PendingHooksAnnotation: "AfterClusterUpgrade,AfterControlPlaneUpgrade",
    63  					},
    64  				},
    65  			},
    66  			hook: runtimehooksv1.AfterClusterUpgrade,
    67  			want: true,
    68  		},
    69  		{
    70  			name: "should return false if the hook is not marked as pending",
    71  			obj: &corev1.ConfigMap{
    72  				ObjectMeta: metav1.ObjectMeta{
    73  					Name:      "test-cluster",
    74  					Namespace: "test-ns",
    75  				},
    76  			},
    77  			hook: runtimehooksv1.AfterClusterUpgrade,
    78  			want: false,
    79  		},
    80  		{
    81  			name: "should return false if the hook is not marked - other hooks are marked as pending",
    82  			obj: &corev1.ConfigMap{
    83  				ObjectMeta: metav1.ObjectMeta{
    84  					Name:      "test-cluster",
    85  					Namespace: "test-ns",
    86  					Annotations: map[string]string{
    87  						runtimev1.PendingHooksAnnotation: "AfterControlPlaneUpgrade",
    88  					},
    89  				},
    90  			},
    91  			hook: runtimehooksv1.AfterClusterUpgrade,
    92  			want: false,
    93  		},
    94  	}
    95  
    96  	for _, tt := range tests {
    97  		t.Run(tt.name, func(t *testing.T) {
    98  			g := NewWithT(t)
    99  			g.Expect(IsPending(tt.hook, tt.obj)).To(Equal(tt.want))
   100  		})
   101  	}
   102  }
   103  
   104  func TestMarkAsPending(t *testing.T) {
   105  	tests := []struct {
   106  		name               string
   107  		obj                client.Object
   108  		hook               runtimecatalog.Hook
   109  		expectedAnnotation string
   110  	}{
   111  		{
   112  			name: "should add the marker if not already marked as pending",
   113  			obj: &corev1.ConfigMap{
   114  				ObjectMeta: metav1.ObjectMeta{
   115  					Name:      "test-cluster",
   116  					Namespace: "test-ns",
   117  				},
   118  			},
   119  			hook:               runtimehooksv1.AfterClusterUpgrade,
   120  			expectedAnnotation: "AfterClusterUpgrade",
   121  		},
   122  		{
   123  			name: "should add the marker if not already marked as pending - other hooks are present",
   124  			obj: &corev1.ConfigMap{
   125  				ObjectMeta: metav1.ObjectMeta{
   126  					Name:      "test-cluster",
   127  					Namespace: "test-ns",
   128  					Annotations: map[string]string{
   129  						runtimev1.PendingHooksAnnotation: "AfterControlPlaneUpgrade",
   130  					},
   131  				},
   132  			},
   133  			hook:               runtimehooksv1.AfterClusterUpgrade,
   134  			expectedAnnotation: "AfterClusterUpgrade,AfterControlPlaneUpgrade",
   135  		},
   136  		{
   137  			name: "should pass if the marker is already marked as pending",
   138  			obj: &corev1.ConfigMap{
   139  				ObjectMeta: metav1.ObjectMeta{
   140  					Name:      "test-cluster",
   141  					Namespace: "test-ns",
   142  					Annotations: map[string]string{
   143  						runtimev1.PendingHooksAnnotation: "AfterClusterUpgrade",
   144  					},
   145  				},
   146  			},
   147  			hook:               runtimehooksv1.AfterClusterUpgrade,
   148  			expectedAnnotation: "AfterClusterUpgrade",
   149  		},
   150  		{
   151  			name: "should pass if the marker is already marked as pending and remove empty string entry",
   152  			obj: &corev1.ConfigMap{
   153  				ObjectMeta: metav1.ObjectMeta{
   154  					Name:      "test-cluster",
   155  					Namespace: "test-ns",
   156  					Annotations: map[string]string{
   157  						runtimev1.PendingHooksAnnotation: ",AfterClusterUpgrade",
   158  					},
   159  				},
   160  			},
   161  			hook:               runtimehooksv1.AfterClusterUpgrade,
   162  			expectedAnnotation: "AfterClusterUpgrade",
   163  		},
   164  	}
   165  
   166  	for _, tt := range tests {
   167  		t.Run(tt.name, func(t *testing.T) {
   168  			g := NewWithT(t)
   169  			fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build()
   170  			ctx := context.Background()
   171  			g.Expect(MarkAsPending(ctx, fakeClient, tt.obj, tt.hook)).To(Succeed())
   172  			annotations := tt.obj.GetAnnotations()
   173  			g.Expect(annotations[runtimev1.PendingHooksAnnotation]).To(ContainSubstring(runtimecatalog.HookName(tt.hook)))
   174  			g.Expect(annotations[runtimev1.PendingHooksAnnotation]).To(Equal(tt.expectedAnnotation))
   175  		})
   176  	}
   177  }
   178  
   179  func TestMarkAsDone(t *testing.T) {
   180  	tests := []struct {
   181  		name               string
   182  		obj                client.Object
   183  		hook               runtimecatalog.Hook
   184  		expectedAnnotation string
   185  	}{
   186  		{
   187  			name: "should pass if the marker is not already present",
   188  			obj: &corev1.ConfigMap{
   189  				ObjectMeta: metav1.ObjectMeta{
   190  					Name:      "test-cluster",
   191  					Namespace: "test-ns",
   192  				},
   193  			},
   194  			hook:               runtimehooksv1.AfterClusterUpgrade,
   195  			expectedAnnotation: "",
   196  		},
   197  		{
   198  			name: "should remove if the marker is already present",
   199  			obj: &corev1.ConfigMap{
   200  				ObjectMeta: metav1.ObjectMeta{
   201  					Name:      "test-cluster",
   202  					Namespace: "test-ns",
   203  					Annotations: map[string]string{
   204  						runtimev1.PendingHooksAnnotation: "AfterClusterUpgrade",
   205  					},
   206  				},
   207  			},
   208  			hook:               runtimehooksv1.AfterClusterUpgrade,
   209  			expectedAnnotation: "",
   210  		},
   211  		{
   212  			name: "should remove if the marker is already present among multiple hooks",
   213  			obj: &corev1.ConfigMap{
   214  				ObjectMeta: metav1.ObjectMeta{
   215  					Name:      "test-cluster",
   216  					Namespace: "test-ns",
   217  					Annotations: map[string]string{
   218  						runtimev1.PendingHooksAnnotation: "AfterClusterUpgrade,AfterControlPlaneUpgrade",
   219  					},
   220  				},
   221  			},
   222  			hook:               runtimehooksv1.AfterClusterUpgrade,
   223  			expectedAnnotation: "AfterControlPlaneUpgrade",
   224  		},
   225  		{
   226  			name: "should remove empty string entry",
   227  			obj: &corev1.ConfigMap{
   228  				ObjectMeta: metav1.ObjectMeta{
   229  					Name:      "test-cluster",
   230  					Namespace: "test-ns",
   231  					Annotations: map[string]string{
   232  						runtimev1.PendingHooksAnnotation: ",AfterClusterUpgrade,AfterControlPlaneUpgrade",
   233  					},
   234  				},
   235  			},
   236  			hook:               runtimehooksv1.AfterClusterUpgrade,
   237  			expectedAnnotation: "AfterControlPlaneUpgrade",
   238  		},
   239  	}
   240  
   241  	for _, tt := range tests {
   242  		t.Run(tt.name, func(t *testing.T) {
   243  			g := NewWithT(t)
   244  			fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build()
   245  			ctx := context.Background()
   246  			g.Expect(MarkAsDone(ctx, fakeClient, tt.obj, tt.hook)).To(Succeed())
   247  			annotations := tt.obj.GetAnnotations()
   248  			g.Expect(annotations[runtimev1.PendingHooksAnnotation]).NotTo(ContainSubstring(runtimecatalog.HookName(tt.hook)))
   249  			g.Expect(annotations[runtimev1.PendingHooksAnnotation]).To(Equal(tt.expectedAnnotation))
   250  		})
   251  	}
   252  }
   253  
   254  func TestIsOkToDelete(t *testing.T) {
   255  	tests := []struct {
   256  		name string
   257  		obj  client.Object
   258  		want bool
   259  	}{
   260  		{
   261  			name: "should return true if the object has the 'ok-to-delete' annotation",
   262  			obj: &corev1.ConfigMap{
   263  				ObjectMeta: metav1.ObjectMeta{
   264  					Name:      "test-cluster",
   265  					Namespace: "test-ns",
   266  					Annotations: map[string]string{
   267  						runtimev1.OkToDeleteAnnotation: "",
   268  					},
   269  				},
   270  			},
   271  			want: true,
   272  		},
   273  		{
   274  			name: "should return false if the object does not have the 'ok-to-delete' annotation",
   275  			obj: &corev1.ConfigMap{
   276  				ObjectMeta: metav1.ObjectMeta{
   277  					Name:      "test-cluster",
   278  					Namespace: "test-ns",
   279  				},
   280  			},
   281  			want: false,
   282  		},
   283  	}
   284  
   285  	for _, tt := range tests {
   286  		t.Run(tt.name, func(t *testing.T) {
   287  			g := NewWithT(t)
   288  			g.Expect(IsOkToDelete(tt.obj)).To(Equal(tt.want))
   289  		})
   290  	}
   291  }
   292  
   293  func TestMarkAsOkToDelete(t *testing.T) {
   294  	tests := []struct {
   295  		name string
   296  		obj  client.Object
   297  	}{
   298  		{
   299  			name: "should add the 'ok-to-delete' annotation on the object if not already present",
   300  			obj: &corev1.ConfigMap{
   301  				ObjectMeta: metav1.ObjectMeta{
   302  					Name:      "test-cluster",
   303  					Namespace: "test-ns",
   304  				},
   305  			},
   306  		},
   307  		{
   308  			name: "should succeed if the 'ok-to-delete' annotation is already present",
   309  			obj: &corev1.ConfigMap{
   310  				ObjectMeta: metav1.ObjectMeta{
   311  					Name:      "test-cluster",
   312  					Namespace: "test-ns",
   313  					Annotations: map[string]string{
   314  						runtimev1.OkToDeleteAnnotation: "",
   315  					},
   316  				},
   317  			},
   318  		},
   319  	}
   320  
   321  	for _, tt := range tests {
   322  		t.Run(tt.name, func(t *testing.T) {
   323  			g := NewWithT(t)
   324  			fakeClient := fake.NewClientBuilder().WithObjects(tt.obj).Build()
   325  			ctx := context.Background()
   326  			g.Expect(MarkAsOkToDelete(ctx, fakeClient, tt.obj)).To(Succeed())
   327  			annotations := tt.obj.GetAnnotations()
   328  			g.Expect(annotations).To(HaveKey(runtimev1.OkToDeleteAnnotation))
   329  		})
   330  	}
   331  }