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 }