sigs.k8s.io/cluster-api@v1.7.1/exp/internal/webhooks/machinepool_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  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/utils/ptr"
    27  	ctrl "sigs.k8s.io/controller-runtime"
    28  
    29  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    30  	expv1 "sigs.k8s.io/cluster-api/exp/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/internal/webhooks/util"
    32  )
    33  
    34  var ctx = ctrl.SetupSignalHandler()
    35  
    36  func TestMachinePoolDefault(t *testing.T) {
    37  	g := NewWithT(t)
    38  
    39  	mp := &expv1.MachinePool{
    40  		ObjectMeta: metav1.ObjectMeta{
    41  			Namespace: "foobar",
    42  		},
    43  		Spec: expv1.MachinePoolSpec{
    44  			Template: clusterv1.MachineTemplateSpec{
    45  				Spec: clusterv1.MachineSpec{
    46  					Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{}},
    47  					Version:   ptr.To("1.20.0"),
    48  				},
    49  			},
    50  		},
    51  	}
    52  	webhook := &MachinePool{}
    53  	t.Run("for MachinePool", util.CustomDefaultValidateTest(ctx, mp, webhook))
    54  	g.Expect(webhook.Default(ctx, mp)).To(Succeed())
    55  
    56  	g.Expect(mp.Labels[clusterv1.ClusterNameLabel]).To(Equal(mp.Spec.ClusterName))
    57  	g.Expect(mp.Spec.Replicas).To(Equal(ptr.To[int32](1)))
    58  	g.Expect(mp.Spec.MinReadySeconds).To(Equal(ptr.To[int32](0)))
    59  	g.Expect(mp.Spec.Template.Spec.Bootstrap.ConfigRef.Namespace).To(Equal(mp.Namespace))
    60  	g.Expect(mp.Spec.Template.Spec.InfrastructureRef.Namespace).To(Equal(mp.Namespace))
    61  	g.Expect(mp.Spec.Template.Spec.Version).To(Equal(ptr.To("v1.20.0")))
    62  }
    63  
    64  func TestMachinePoolBootstrapValidation(t *testing.T) {
    65  	tests := []struct {
    66  		name      string
    67  		bootstrap clusterv1.Bootstrap
    68  		expectErr bool
    69  	}{
    70  		{
    71  			name:      "should return error if configref and data are nil",
    72  			bootstrap: clusterv1.Bootstrap{ConfigRef: nil, DataSecretName: nil},
    73  			expectErr: true,
    74  		},
    75  		{
    76  			name:      "should not return error if dataSecretName is set",
    77  			bootstrap: clusterv1.Bootstrap{ConfigRef: nil, DataSecretName: ptr.To("test")},
    78  			expectErr: false,
    79  		},
    80  		{
    81  			name:      "should not return error if config ref is set",
    82  			bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{}, DataSecretName: nil},
    83  			expectErr: false,
    84  		},
    85  	}
    86  
    87  	for _, tt := range tests {
    88  		t.Run(tt.name, func(t *testing.T) {
    89  			g := NewWithT(t)
    90  			webhook := &MachinePool{}
    91  			mp := &expv1.MachinePool{
    92  				Spec: expv1.MachinePoolSpec{
    93  					Template: clusterv1.MachineTemplateSpec{
    94  						Spec: clusterv1.MachineSpec{
    95  							Bootstrap: tt.bootstrap,
    96  						},
    97  					},
    98  				},
    99  			}
   100  
   101  			if tt.expectErr {
   102  				warnings, err := webhook.ValidateCreate(ctx, mp)
   103  				g.Expect(err).To(HaveOccurred())
   104  				g.Expect(warnings).To(BeEmpty())
   105  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   106  				g.Expect(err).To(HaveOccurred())
   107  				g.Expect(warnings).To(BeEmpty())
   108  			} else {
   109  				warnings, err := webhook.ValidateCreate(ctx, mp)
   110  				g.Expect(err).ToNot(HaveOccurred())
   111  				g.Expect(warnings).To(BeEmpty())
   112  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   113  				g.Expect(err).ToNot(HaveOccurred())
   114  				g.Expect(warnings).To(BeEmpty())
   115  			}
   116  		})
   117  	}
   118  }
   119  
   120  func TestMachinePoolNamespaceValidation(t *testing.T) {
   121  	tests := []struct {
   122  		name      string
   123  		expectErr bool
   124  		bootstrap clusterv1.Bootstrap
   125  		infraRef  corev1.ObjectReference
   126  		namespace string
   127  	}{
   128  		{
   129  			name:      "should succeed if all namespaces match",
   130  			expectErr: false,
   131  			namespace: "foobar",
   132  			bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{Namespace: "foobar"}},
   133  			infraRef:  corev1.ObjectReference{Namespace: "foobar"},
   134  		},
   135  		{
   136  			name:      "should return error if namespace and bootstrap namespace don't match",
   137  			expectErr: true,
   138  			namespace: "foobar",
   139  			bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{Namespace: "foobar123"}},
   140  			infraRef:  corev1.ObjectReference{Namespace: "foobar"},
   141  		},
   142  		{
   143  			name:      "should return error if namespace and infrastructure ref namespace don't match",
   144  			expectErr: true,
   145  			namespace: "foobar",
   146  			bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{Namespace: "foobar"}},
   147  			infraRef:  corev1.ObjectReference{Namespace: "foobar123"},
   148  		},
   149  		{
   150  			name:      "should return error if no namespaces match",
   151  			expectErr: true,
   152  			namespace: "foobar1",
   153  			bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{Namespace: "foobar2"}},
   154  			infraRef:  corev1.ObjectReference{Namespace: "foobar3"},
   155  		},
   156  	}
   157  
   158  	for _, tt := range tests {
   159  		t.Run(tt.name, func(t *testing.T) {
   160  			g := NewWithT(t)
   161  
   162  			webhook := &MachinePool{}
   163  			mp := &expv1.MachinePool{
   164  				ObjectMeta: metav1.ObjectMeta{Namespace: tt.namespace},
   165  				Spec: expv1.MachinePoolSpec{
   166  					Template: clusterv1.MachineTemplateSpec{
   167  						Spec: clusterv1.MachineSpec{
   168  							Bootstrap:         tt.bootstrap,
   169  							InfrastructureRef: tt.infraRef,
   170  						},
   171  					},
   172  				},
   173  			}
   174  
   175  			if tt.expectErr {
   176  				warnings, err := webhook.ValidateCreate(ctx, mp)
   177  				g.Expect(err).To(HaveOccurred())
   178  				g.Expect(warnings).To(BeEmpty())
   179  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   180  				g.Expect(err).To(HaveOccurred())
   181  				g.Expect(warnings).To(BeEmpty())
   182  			} else {
   183  				warnings, err := webhook.ValidateCreate(ctx, mp)
   184  				g.Expect(err).ToNot(HaveOccurred())
   185  				g.Expect(warnings).To(BeEmpty())
   186  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   187  				g.Expect(err).ToNot(HaveOccurred())
   188  				g.Expect(warnings).To(BeEmpty())
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestMachinePoolClusterNameImmutable(t *testing.T) {
   195  	tests := []struct {
   196  		name           string
   197  		oldClusterName string
   198  		newClusterName string
   199  		expectErr      bool
   200  	}{
   201  		{
   202  			name:           "when the cluster name has not changed",
   203  			oldClusterName: "foo",
   204  			newClusterName: "foo",
   205  			expectErr:      false,
   206  		},
   207  		{
   208  			name:           "when the cluster name has changed",
   209  			oldClusterName: "foo",
   210  			newClusterName: "bar",
   211  			expectErr:      true,
   212  		},
   213  	}
   214  
   215  	for _, tt := range tests {
   216  		t.Run(tt.name, func(t *testing.T) {
   217  			g := NewWithT(t)
   218  
   219  			newMP := &expv1.MachinePool{
   220  				Spec: expv1.MachinePoolSpec{
   221  					ClusterName: tt.newClusterName,
   222  					Template: clusterv1.MachineTemplateSpec{
   223  						Spec: clusterv1.MachineSpec{
   224  							Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{}},
   225  						},
   226  					},
   227  				},
   228  			}
   229  
   230  			oldMP := &expv1.MachinePool{
   231  				Spec: expv1.MachinePoolSpec{
   232  					ClusterName: tt.oldClusterName,
   233  					Template: clusterv1.MachineTemplateSpec{
   234  						Spec: clusterv1.MachineSpec{
   235  							Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{}},
   236  						},
   237  					},
   238  				},
   239  			}
   240  
   241  			webhook := MachinePool{}
   242  			warnings, err := webhook.ValidateUpdate(ctx, oldMP, newMP)
   243  			if tt.expectErr {
   244  				g.Expect(err).To(HaveOccurred())
   245  			} else {
   246  				g.Expect(err).ToNot(HaveOccurred())
   247  			}
   248  			g.Expect(warnings).To(BeEmpty())
   249  		})
   250  	}
   251  }
   252  
   253  func TestMachinePoolVersionValidation(t *testing.T) {
   254  	tests := []struct {
   255  		name      string
   256  		expectErr bool
   257  		version   string
   258  	}{
   259  		{
   260  			name:      "should succeed version is a valid kube semver",
   261  			expectErr: false,
   262  			version:   "v1.23.3",
   263  		},
   264  		{
   265  			name:      "should succeed version is a valid pre-release",
   266  			expectErr: false,
   267  			version:   "v1.19.0-alpha.1",
   268  		},
   269  		{
   270  			name:      "should fail if version is not a valid semver",
   271  			expectErr: true,
   272  			version:   "v1.1",
   273  		},
   274  		{
   275  			name:      "should fail if version is missing a v prefix",
   276  			expectErr: true,
   277  			version:   "1.20.0",
   278  		},
   279  	}
   280  
   281  	for i := range tests {
   282  		tt := tests[i]
   283  		t.Run(tt.name, func(t *testing.T) {
   284  			g := NewWithT(t)
   285  
   286  			mp := &expv1.MachinePool{
   287  				Spec: expv1.MachinePoolSpec{
   288  					Template: clusterv1.MachineTemplateSpec{
   289  						Spec: clusterv1.MachineSpec{
   290  							Bootstrap: clusterv1.Bootstrap{ConfigRef: &corev1.ObjectReference{}},
   291  							Version:   &tt.version,
   292  						},
   293  					},
   294  				},
   295  			}
   296  			webhook := &MachinePool{}
   297  
   298  			if tt.expectErr {
   299  				warnings, err := webhook.ValidateCreate(ctx, mp)
   300  				g.Expect(err).To(HaveOccurred())
   301  				g.Expect(warnings).To(BeEmpty())
   302  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   303  				g.Expect(err).To(HaveOccurred())
   304  				g.Expect(warnings).To(BeEmpty())
   305  			} else {
   306  				warnings, err := webhook.ValidateCreate(ctx, mp)
   307  				g.Expect(err).ToNot(HaveOccurred())
   308  				g.Expect(warnings).To(BeEmpty())
   309  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   310  				g.Expect(err).ToNot(HaveOccurred())
   311  				g.Expect(warnings).To(BeEmpty())
   312  			}
   313  		})
   314  	}
   315  }
   316  
   317  func TestMachinePoolMetadataValidation(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  			mp := &expv1.MachinePool{
   342  				Spec: expv1.MachinePoolSpec{
   343  					Template: clusterv1.MachineTemplateSpec{
   344  						ObjectMeta: clusterv1.ObjectMeta{
   345  							Labels:      tt.labels,
   346  							Annotations: tt.annotations,
   347  						},
   348  					},
   349  				},
   350  			}
   351  			webhook := &MachinePool{}
   352  			if tt.expectErr {
   353  				warnings, err := webhook.ValidateCreate(ctx, mp)
   354  				g.Expect(err).To(HaveOccurred())
   355  				g.Expect(warnings).To(BeEmpty())
   356  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   357  				g.Expect(err).To(HaveOccurred())
   358  				g.Expect(warnings).To(BeEmpty())
   359  			} else {
   360  				warnings, err := webhook.ValidateCreate(ctx, mp)
   361  				g.Expect(err).ToNot(HaveOccurred())
   362  				g.Expect(warnings).To(BeEmpty())
   363  				warnings, err = webhook.ValidateUpdate(ctx, mp, mp)
   364  				g.Expect(err).ToNot(HaveOccurred())
   365  				g.Expect(warnings).To(BeEmpty())
   366  			}
   367  		})
   368  	}
   369  }