sigs.k8s.io/cluster-api@v1.6.3/internal/webhooks/machineset_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 webhooks 18 19 import ( 20 "strings" 21 "testing" 22 23 . "github.com/onsi/gomega" 24 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 25 "k8s.io/utils/pointer" 26 27 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 28 "sigs.k8s.io/cluster-api/internal/webhooks/util" 29 ) 30 31 func TestMachineSetDefault(t *testing.T) { 32 g := NewWithT(t) 33 ms := &clusterv1.MachineSet{ 34 ObjectMeta: metav1.ObjectMeta{ 35 Name: "test-ms", 36 }, 37 Spec: clusterv1.MachineSetSpec{ 38 Template: clusterv1.MachineTemplateSpec{ 39 Spec: clusterv1.MachineSpec{ 40 Version: pointer.String("1.19.10"), 41 }, 42 }, 43 }, 44 } 45 webhook := &MachineSet{} 46 47 t.Run("for MachineSet", util.CustomDefaultValidateTest(ctx, ms, webhook)) 48 g.Expect(webhook.Default(ctx, ms)).To(Succeed()) 49 50 g.Expect(ms.Labels[clusterv1.ClusterNameLabel]).To(Equal(ms.Spec.ClusterName)) 51 g.Expect(ms.Spec.DeletePolicy).To(Equal(string(clusterv1.RandomMachineSetDeletePolicy))) 52 g.Expect(ms.Spec.Selector.MatchLabels).To(HaveKeyWithValue(clusterv1.MachineSetNameLabel, "test-ms")) 53 g.Expect(ms.Spec.Template.Labels).To(HaveKeyWithValue(clusterv1.MachineSetNameLabel, "test-ms")) 54 g.Expect(*ms.Spec.Template.Spec.Version).To(Equal("v1.19.10")) 55 } 56 57 func TestMachineSetLabelSelectorMatchValidation(t *testing.T) { 58 tests := []struct { 59 name string 60 selectors map[string]string 61 labels map[string]string 62 expectErr bool 63 }{ 64 { 65 name: "should return error on mismatch", 66 selectors: map[string]string{"foo": "bar"}, 67 labels: map[string]string{"foo": "baz"}, 68 expectErr: true, 69 }, 70 { 71 name: "should return error on missing labels", 72 selectors: map[string]string{"foo": "bar"}, 73 labels: map[string]string{"": ""}, 74 expectErr: true, 75 }, 76 { 77 name: "should return error if all selectors don't match", 78 selectors: map[string]string{"foo": "bar", "hello": "world"}, 79 labels: map[string]string{"foo": "bar"}, 80 expectErr: true, 81 }, 82 { 83 name: "should not return error on match", 84 selectors: map[string]string{"foo": "bar"}, 85 labels: map[string]string{"foo": "bar"}, 86 expectErr: false, 87 }, 88 { 89 name: "should return error for invalid selector", 90 selectors: map[string]string{"-123-foo": "bar"}, 91 labels: map[string]string{"-123-foo": "bar"}, 92 expectErr: true, 93 }, 94 } 95 96 for _, tt := range tests { 97 t.Run(tt.name, func(t *testing.T) { 98 g := NewWithT(t) 99 ms := &clusterv1.MachineSet{ 100 Spec: clusterv1.MachineSetSpec{ 101 Selector: metav1.LabelSelector{ 102 MatchLabels: tt.selectors, 103 }, 104 Template: clusterv1.MachineTemplateSpec{ 105 ObjectMeta: clusterv1.ObjectMeta{ 106 Labels: tt.labels, 107 }, 108 }, 109 }, 110 } 111 webhook := &MachineSet{} 112 113 if tt.expectErr { 114 warnings, err := webhook.ValidateCreate(ctx, ms) 115 g.Expect(err).To(HaveOccurred()) 116 g.Expect(warnings).To(BeEmpty()) 117 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 118 g.Expect(err).To(HaveOccurred()) 119 g.Expect(warnings).To(BeEmpty()) 120 } else { 121 warnings, err := webhook.ValidateCreate(ctx, ms) 122 g.Expect(err).ToNot(HaveOccurred()) 123 g.Expect(warnings).To(BeEmpty()) 124 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 125 g.Expect(err).ToNot(HaveOccurred()) 126 g.Expect(warnings).To(BeEmpty()) 127 } 128 }) 129 } 130 } 131 132 func TestMachineSetClusterNameImmutable(t *testing.T) { 133 tests := []struct { 134 name string 135 oldClusterName string 136 newClusterName string 137 expectErr bool 138 }{ 139 { 140 name: "when the cluster name has not changed", 141 oldClusterName: "foo", 142 newClusterName: "foo", 143 expectErr: false, 144 }, 145 { 146 name: "when the cluster name has changed", 147 oldClusterName: "foo", 148 newClusterName: "bar", 149 expectErr: true, 150 }, 151 } 152 153 for _, tt := range tests { 154 t.Run(tt.name, func(t *testing.T) { 155 g := NewWithT(t) 156 157 newMS := &clusterv1.MachineSet{ 158 Spec: clusterv1.MachineSetSpec{ 159 ClusterName: tt.newClusterName, 160 }, 161 } 162 163 oldMS := &clusterv1.MachineSet{ 164 Spec: clusterv1.MachineSetSpec{ 165 ClusterName: tt.oldClusterName, 166 }, 167 } 168 169 warnings, err := (&MachineSet{}).ValidateUpdate(ctx, oldMS, newMS) 170 if tt.expectErr { 171 g.Expect(err).To(HaveOccurred()) 172 } else { 173 g.Expect(err).ToNot(HaveOccurred()) 174 } 175 g.Expect(warnings).To(BeEmpty()) 176 }) 177 } 178 } 179 180 func TestMachineSetVersionValidation(t *testing.T) { 181 tests := []struct { 182 name string 183 version string 184 expectErr bool 185 }{ 186 { 187 name: "should succeed when given a valid semantic version with prepended 'v'", 188 version: "v1.19.2", 189 expectErr: false, 190 }, 191 { 192 name: "should return error when given a valid semantic version without 'v'", 193 version: "1.19.2", 194 expectErr: true, 195 }, 196 { 197 name: "should return error when given an invalid semantic version", 198 version: "1", 199 expectErr: true, 200 }, 201 { 202 name: "should return error when given an invalid semantic version", 203 version: "v1", 204 expectErr: true, 205 }, 206 { 207 name: "should return error when given an invalid semantic version", 208 version: "wrong_version", 209 expectErr: true, 210 }, 211 } 212 213 for _, tt := range tests { 214 t.Run(tt.name, func(t *testing.T) { 215 g := NewWithT(t) 216 217 ms := &clusterv1.MachineSet{ 218 Spec: clusterv1.MachineSetSpec{ 219 Template: clusterv1.MachineTemplateSpec{ 220 Spec: clusterv1.MachineSpec{ 221 Version: pointer.String(tt.version), 222 }, 223 }, 224 }, 225 } 226 webhook := &MachineSet{} 227 228 if tt.expectErr { 229 warnings, err := webhook.ValidateCreate(ctx, ms) 230 g.Expect(err).To(HaveOccurred()) 231 g.Expect(warnings).To(BeEmpty()) 232 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 233 g.Expect(err).To(HaveOccurred()) 234 g.Expect(warnings).To(BeEmpty()) 235 } else { 236 warnings, err := webhook.ValidateCreate(ctx, ms) 237 g.Expect(err).ToNot(HaveOccurred()) 238 g.Expect(warnings).To(BeEmpty()) 239 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 240 g.Expect(err).ToNot(HaveOccurred()) 241 g.Expect(warnings).To(BeEmpty()) 242 } 243 }) 244 } 245 } 246 247 func TestValidateSkippedMachineSetPreflightChecks(t *testing.T) { 248 tests := []struct { 249 name string 250 ms *clusterv1.MachineSet 251 expectErr bool 252 }{ 253 { 254 name: "should pass if the machine set skip preflight checks annotation is not set", 255 ms: &clusterv1.MachineSet{}, 256 expectErr: false, 257 }, 258 { 259 name: "should pass if not preflight checks are skipped", 260 ms: &clusterv1.MachineSet{ 261 ObjectMeta: metav1.ObjectMeta{ 262 Annotations: map[string]string{ 263 clusterv1.MachineSetSkipPreflightChecksAnnotation: "", 264 }, 265 }, 266 }, 267 expectErr: false, 268 }, 269 { 270 name: "should pass if only valid preflight checks are skipped (single)", 271 ms: &clusterv1.MachineSet{ 272 ObjectMeta: metav1.ObjectMeta{ 273 Annotations: map[string]string{ 274 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckKubeadmVersionSkew), 275 }, 276 }, 277 }, 278 expectErr: false, 279 }, 280 { 281 name: "should pass if only valid preflight checks are skipped (multiple)", 282 ms: &clusterv1.MachineSet{ 283 ObjectMeta: metav1.ObjectMeta{ 284 Annotations: map[string]string{ 285 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckKubeadmVersionSkew) + "," + string(clusterv1.MachineSetPreflightCheckControlPlaneIsStable), 286 }, 287 }, 288 }, 289 expectErr: false, 290 }, 291 { 292 name: "should fail if invalid preflight checks are skipped", 293 ms: &clusterv1.MachineSet{ 294 ObjectMeta: metav1.ObjectMeta{ 295 Annotations: map[string]string{ 296 clusterv1.MachineSetSkipPreflightChecksAnnotation: string(clusterv1.MachineSetPreflightCheckKubeadmVersionSkew) + ",invalid-preflight-check-name", 297 }, 298 }, 299 }, 300 expectErr: true, 301 }, 302 } 303 304 for _, tt := range tests { 305 t.Run(tt.name, func(t *testing.T) { 306 g := NewWithT(t) 307 err := validateSkippedMachineSetPreflightChecks(tt.ms) 308 if tt.expectErr { 309 g.Expect(err).To(HaveOccurred()) 310 } else { 311 g.Expect(err).ToNot(HaveOccurred()) 312 } 313 }) 314 } 315 } 316 317 func TestMachineSetTemplateMetadataValidation(t *testing.T) { 318 tests := []struct { 319 name string 320 labels map[string]string 321 annotations map[string]string 322 expectErr bool 323 }{ 324 { 325 name: "should return error for invalid labels and annotations", 326 labels: map[string]string{ 327 "foo": "$invalid-key", 328 "bar": strings.Repeat("a", 64) + "too-long-value", 329 "/invalid-key": "foo", 330 }, 331 annotations: map[string]string{ 332 "/invalid-key": "foo", 333 }, 334 expectErr: true, 335 }, 336 } 337 338 for _, tt := range tests { 339 t.Run(tt.name, func(t *testing.T) { 340 g := NewWithT(t) 341 ms := &clusterv1.MachineSet{ 342 Spec: clusterv1.MachineSetSpec{ 343 Template: clusterv1.MachineTemplateSpec{ 344 ObjectMeta: clusterv1.ObjectMeta{ 345 Labels: tt.labels, 346 Annotations: tt.annotations, 347 }, 348 }, 349 }, 350 } 351 352 webhook := &MachineSet{} 353 354 if tt.expectErr { 355 warnings, err := webhook.ValidateCreate(ctx, ms) 356 g.Expect(err).To(HaveOccurred()) 357 g.Expect(warnings).To(BeEmpty()) 358 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 359 g.Expect(err).To(HaveOccurred()) 360 g.Expect(warnings).To(BeEmpty()) 361 } else { 362 warnings, err := webhook.ValidateCreate(ctx, ms) 363 g.Expect(err).ToNot(HaveOccurred()) 364 g.Expect(warnings).To(BeEmpty()) 365 warnings, err = webhook.ValidateUpdate(ctx, ms, ms) 366 g.Expect(err).ToNot(HaveOccurred()) 367 g.Expect(warnings).To(BeEmpty()) 368 } 369 }) 370 } 371 }