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  }