sigs.k8s.io/cluster-api@v1.7.1/internal/webhooks/machinedeployment_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  	"context"
    21  	"strings"
    22  	"testing"
    23  
    24  	. "github.com/onsi/gomega"
    25  	admissionv1 "k8s.io/api/admission/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/util/intstr"
    29  	"k8s.io/utils/ptr"
    30  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    31  
    32  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    33  	"sigs.k8s.io/cluster-api/internal/webhooks/util"
    34  )
    35  
    36  func TestMachineDeploymentDefault(t *testing.T) {
    37  	g := NewWithT(t)
    38  	md := &clusterv1.MachineDeployment{
    39  		ObjectMeta: metav1.ObjectMeta{
    40  			Name: "test-md",
    41  		},
    42  		Spec: clusterv1.MachineDeploymentSpec{
    43  			ClusterName: "test-cluster",
    44  			Template: clusterv1.MachineTemplateSpec{
    45  				Spec: clusterv1.MachineSpec{
    46  					Version: ptr.To("1.19.10"),
    47  				},
    48  			},
    49  		},
    50  	}
    51  
    52  	scheme := runtime.NewScheme()
    53  	g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
    54  	webhook := &MachineDeployment{
    55  		decoder: admission.NewDecoder(scheme),
    56  	}
    57  
    58  	reqCtx := admission.NewContextWithRequest(ctx, admission.Request{
    59  		AdmissionRequest: admissionv1.AdmissionRequest{
    60  			Operation: admissionv1.Create,
    61  		},
    62  	})
    63  	t.Run("for MachineDeployment", util.CustomDefaultValidateTest(reqCtx, md, webhook))
    64  
    65  	g.Expect(webhook.Default(reqCtx, md)).To(Succeed())
    66  
    67  	g.Expect(md.Labels[clusterv1.ClusterNameLabel]).To(Equal(md.Spec.ClusterName))
    68  
    69  	g.Expect(md.Spec.MinReadySeconds).To(Equal(ptr.To[int32](0)))
    70  	g.Expect(md.Spec.Replicas).To(Equal(ptr.To[int32](1)))
    71  	g.Expect(md.Spec.RevisionHistoryLimit).To(Equal(ptr.To[int32](1)))
    72  	g.Expect(md.Spec.ProgressDeadlineSeconds).To(Equal(ptr.To[int32](600)))
    73  	g.Expect(md.Spec.Strategy).ToNot(BeNil())
    74  
    75  	g.Expect(md.Spec.Selector.MatchLabels).To(HaveKeyWithValue(clusterv1.MachineDeploymentNameLabel, "test-md"))
    76  	g.Expect(md.Spec.Template.Labels).To(HaveKeyWithValue(clusterv1.MachineDeploymentNameLabel, "test-md"))
    77  	g.Expect(md.Spec.Selector.MatchLabels).To(HaveKeyWithValue(clusterv1.ClusterNameLabel, "test-cluster"))
    78  	g.Expect(md.Spec.Template.Labels).To(HaveKeyWithValue(clusterv1.ClusterNameLabel, "test-cluster"))
    79  
    80  	g.Expect(md.Spec.Strategy.Type).To(Equal(clusterv1.RollingUpdateMachineDeploymentStrategyType))
    81  	g.Expect(md.Spec.Strategy.RollingUpdate).ToNot(BeNil())
    82  	g.Expect(md.Spec.Strategy.RollingUpdate.MaxSurge.IntValue()).To(Equal(1))
    83  	g.Expect(md.Spec.Strategy.RollingUpdate.MaxUnavailable.IntValue()).To(Equal(0))
    84  
    85  	g.Expect(*md.Spec.Template.Spec.Version).To(Equal("v1.19.10"))
    86  }
    87  
    88  func TestCalculateMachineDeploymentReplicas(t *testing.T) {
    89  	tests := []struct {
    90  		name             string
    91  		newMD            *clusterv1.MachineDeployment
    92  		oldMD            *clusterv1.MachineDeployment
    93  		expectedReplicas int32
    94  		expectErr        bool
    95  	}{
    96  		{
    97  			name: "if new MD has replicas set, keep that value",
    98  			newMD: &clusterv1.MachineDeployment{
    99  				Spec: clusterv1.MachineDeploymentSpec{
   100  					Replicas: ptr.To[int32](5),
   101  				},
   102  			},
   103  			expectedReplicas: 5,
   104  		},
   105  		{
   106  			name:             "if new MD does not have replicas set and no annotations, use 1",
   107  			newMD:            &clusterv1.MachineDeployment{},
   108  			expectedReplicas: 1,
   109  		},
   110  		{
   111  			name: "if new MD only has min size annotation, fallback to 1",
   112  			newMD: &clusterv1.MachineDeployment{
   113  				ObjectMeta: metav1.ObjectMeta{
   114  					Annotations: map[string]string{
   115  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   116  					},
   117  				},
   118  			},
   119  			expectedReplicas: 1,
   120  		},
   121  		{
   122  			name: "if new MD only has max size annotation, fallback to 1",
   123  			newMD: &clusterv1.MachineDeployment{
   124  				ObjectMeta: metav1.ObjectMeta{
   125  					Annotations: map[string]string{
   126  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   127  					},
   128  				},
   129  			},
   130  			expectedReplicas: 1,
   131  		},
   132  		{
   133  			name: "if new MD has min and max size annotation and min size is invalid, fail",
   134  			newMD: &clusterv1.MachineDeployment{
   135  				ObjectMeta: metav1.ObjectMeta{
   136  					Annotations: map[string]string{
   137  						clusterv1.AutoscalerMinSizeAnnotation: "abc",
   138  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   139  					},
   140  				},
   141  			},
   142  			expectErr: true,
   143  		},
   144  		{
   145  			name: "if new MD has min and max size annotation and max size is invalid, fail",
   146  			newMD: &clusterv1.MachineDeployment{
   147  				ObjectMeta: metav1.ObjectMeta{
   148  					Annotations: map[string]string{
   149  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   150  						clusterv1.AutoscalerMaxSizeAnnotation: "abc",
   151  					},
   152  				},
   153  			},
   154  			expectErr: true,
   155  		},
   156  		{
   157  			name: "if new MD has min and max size annotation and new MD is a new MD, use min size",
   158  			newMD: &clusterv1.MachineDeployment{
   159  				ObjectMeta: metav1.ObjectMeta{
   160  					Annotations: map[string]string{
   161  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   162  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   163  					},
   164  				},
   165  			},
   166  			expectedReplicas: 3,
   167  		},
   168  		{
   169  			name: "if new MD has min and max size annotation and old MD doesn't have replicas set, use min size",
   170  			newMD: &clusterv1.MachineDeployment{
   171  				ObjectMeta: metav1.ObjectMeta{
   172  					Annotations: map[string]string{
   173  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   174  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   175  					},
   176  				},
   177  			},
   178  			oldMD:            &clusterv1.MachineDeployment{},
   179  			expectedReplicas: 3,
   180  		},
   181  		{
   182  			name: "if new MD has min and max size annotation and old MD replicas is below min size, use min size",
   183  			newMD: &clusterv1.MachineDeployment{
   184  				ObjectMeta: metav1.ObjectMeta{
   185  					Annotations: map[string]string{
   186  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   187  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   188  					},
   189  				},
   190  			},
   191  			oldMD: &clusterv1.MachineDeployment{
   192  				Spec: clusterv1.MachineDeploymentSpec{
   193  					Replicas: ptr.To[int32](1),
   194  				},
   195  			},
   196  			expectedReplicas: 3,
   197  		},
   198  		{
   199  			name: "if new MD has min and max size annotation and old MD replicas is above max size, use max size",
   200  			newMD: &clusterv1.MachineDeployment{
   201  				ObjectMeta: metav1.ObjectMeta{
   202  					Annotations: map[string]string{
   203  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   204  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   205  					},
   206  				},
   207  			},
   208  			oldMD: &clusterv1.MachineDeployment{
   209  				Spec: clusterv1.MachineDeploymentSpec{
   210  					Replicas: ptr.To[int32](15),
   211  				},
   212  			},
   213  			expectedReplicas: 7,
   214  		},
   215  		{
   216  			name: "if new MD has min and max size annotation and old MD replicas is between min and max size, use old MD replicas",
   217  			newMD: &clusterv1.MachineDeployment{
   218  				ObjectMeta: metav1.ObjectMeta{
   219  					Annotations: map[string]string{
   220  						clusterv1.AutoscalerMinSizeAnnotation: "3",
   221  						clusterv1.AutoscalerMaxSizeAnnotation: "7",
   222  					},
   223  				},
   224  			},
   225  			oldMD: &clusterv1.MachineDeployment{
   226  				Spec: clusterv1.MachineDeploymentSpec{
   227  					Replicas: ptr.To[int32](4),
   228  				},
   229  			},
   230  			expectedReplicas: 4,
   231  		},
   232  	}
   233  
   234  	for _, tt := range tests {
   235  		t.Run(tt.name, func(t *testing.T) {
   236  			g := NewWithT(t)
   237  
   238  			replicas, err := calculateMachineDeploymentReplicas(context.Background(), tt.oldMD, tt.newMD, false)
   239  
   240  			if tt.expectErr {
   241  				g.Expect(err).To(HaveOccurred())
   242  				return
   243  			}
   244  
   245  			g.Expect(err).ToNot(HaveOccurred())
   246  			g.Expect(replicas).To(Equal(tt.expectedReplicas))
   247  		})
   248  	}
   249  }
   250  
   251  func TestMachineDeploymentValidation(t *testing.T) {
   252  	badMaxSurge := intstr.FromString("1")
   253  	badMaxUnavailable := intstr.FromString("0")
   254  
   255  	goodMaxSurgePercentage := intstr.FromString("1%")
   256  	goodMaxUnavailablePercentage := intstr.FromString("0%")
   257  
   258  	goodMaxSurgeInt := intstr.FromInt(1)
   259  	goodMaxUnavailableInt := intstr.FromInt(0)
   260  	tests := []struct {
   261  		name      string
   262  		md        *clusterv1.MachineDeployment
   263  		mdName    string
   264  		selectors map[string]string
   265  		labels    map[string]string
   266  		strategy  clusterv1.MachineDeploymentStrategy
   267  		expectErr bool
   268  	}{
   269  		{
   270  			name:      "pass with name of under 63 characters",
   271  			mdName:    "short-name",
   272  			expectErr: false,
   273  		},
   274  		{
   275  			name:      "pass with _, -, . characters in name",
   276  			mdName:    "thisNameContains.A_Non-Alphanumeric",
   277  			expectErr: false,
   278  		},
   279  		{
   280  			name:      "error with name of more than 63 characters",
   281  			mdName:    "thisNameIsReallyMuchLongerThanTheMaximumLengthOfSixtyThreeCharacters",
   282  			expectErr: true,
   283  		},
   284  		{
   285  			name:      "error when name starts with NonAlphanumeric character",
   286  			mdName:    "-thisNameStartsWithANonAlphanumeric",
   287  			expectErr: true,
   288  		},
   289  		{
   290  			name:      "error when name ends with NonAlphanumeric character",
   291  			mdName:    "thisNameEndsWithANonAlphanumeric.",
   292  			expectErr: true,
   293  		},
   294  		{
   295  			name:      "error when name contains invalid NonAlphanumeric character",
   296  			mdName:    "thisNameContainsInvalid!@NonAlphanumerics",
   297  			expectErr: true,
   298  		},
   299  		{
   300  			name:      "should return error on mismatch",
   301  			selectors: map[string]string{"foo": "bar"},
   302  			labels:    map[string]string{"foo": "baz"},
   303  			expectErr: true,
   304  		},
   305  		{
   306  			name:      "should return error on missing labels",
   307  			selectors: map[string]string{"foo": "bar"},
   308  			labels:    map[string]string{"": ""},
   309  			expectErr: true,
   310  		},
   311  		{
   312  			name:      "should return error if all selectors don't match",
   313  			selectors: map[string]string{"foo": "bar", "hello": "world"},
   314  			labels:    map[string]string{"foo": "bar"},
   315  			expectErr: true,
   316  		},
   317  		{
   318  			name:      "should not return error on match",
   319  			selectors: map[string]string{"foo": "bar"},
   320  			labels:    map[string]string{"foo": "bar"},
   321  			expectErr: false,
   322  		},
   323  		{
   324  			name:      "should return error for invalid selector",
   325  			selectors: map[string]string{"-123-foo": "bar"},
   326  			labels:    map[string]string{"-123-foo": "bar"},
   327  			expectErr: true,
   328  		},
   329  		{
   330  			name:      "should return error for invalid maxSurge",
   331  			selectors: map[string]string{"foo": "bar"},
   332  			labels:    map[string]string{"foo": "bar"},
   333  			strategy: clusterv1.MachineDeploymentStrategy{
   334  				Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   335  				RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   336  					MaxUnavailable: &goodMaxUnavailableInt,
   337  					MaxSurge:       &badMaxSurge,
   338  				},
   339  			},
   340  			expectErr: true,
   341  		},
   342  		{
   343  			name:      "should return error for invalid maxUnavailable",
   344  			selectors: map[string]string{"foo": "bar"},
   345  			labels:    map[string]string{"foo": "bar"},
   346  			strategy: clusterv1.MachineDeploymentStrategy{
   347  				Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   348  				RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   349  					MaxUnavailable: &badMaxUnavailable,
   350  					MaxSurge:       &goodMaxSurgeInt,
   351  				},
   352  			},
   353  			expectErr: true,
   354  		},
   355  		{
   356  			name:      "should not return error for valid int maxSurge and maxUnavailable",
   357  			selectors: map[string]string{"foo": "bar"},
   358  			labels:    map[string]string{"foo": "bar"},
   359  			strategy: clusterv1.MachineDeploymentStrategy{
   360  				Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   361  				RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   362  					MaxUnavailable: &goodMaxUnavailableInt,
   363  					MaxSurge:       &goodMaxSurgeInt,
   364  				},
   365  			},
   366  			expectErr: false,
   367  		},
   368  		{
   369  			name:      "should not return error for valid percentage string maxSurge and maxUnavailable",
   370  			selectors: map[string]string{"foo": "bar"},
   371  			labels:    map[string]string{"foo": "bar"},
   372  			strategy: clusterv1.MachineDeploymentStrategy{
   373  				Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   374  				RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   375  					MaxUnavailable: &goodMaxUnavailablePercentage,
   376  					MaxSurge:       &goodMaxSurgePercentage,
   377  				},
   378  			},
   379  			expectErr: false,
   380  		},
   381  	}
   382  
   383  	for i := range tests {
   384  		tt := tests[i]
   385  		t.Run(tt.name, func(t *testing.T) {
   386  			g := NewWithT(t)
   387  			md := &clusterv1.MachineDeployment{
   388  				ObjectMeta: metav1.ObjectMeta{
   389  					Name: tt.mdName,
   390  				},
   391  				Spec: clusterv1.MachineDeploymentSpec{
   392  					Strategy: &tt.strategy,
   393  					Selector: metav1.LabelSelector{
   394  						MatchLabels: tt.selectors,
   395  					},
   396  					Template: clusterv1.MachineTemplateSpec{
   397  						ObjectMeta: clusterv1.ObjectMeta{
   398  							Labels: tt.labels,
   399  						},
   400  					},
   401  				},
   402  			}
   403  
   404  			scheme := runtime.NewScheme()
   405  			g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
   406  			webhook := MachineDeployment{
   407  				decoder: admission.NewDecoder(scheme),
   408  			}
   409  
   410  			if tt.expectErr {
   411  				warnings, err := webhook.ValidateCreate(ctx, md)
   412  				g.Expect(err).To(HaveOccurred())
   413  				g.Expect(warnings).To(BeEmpty())
   414  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   415  				g.Expect(err).To(HaveOccurred())
   416  				g.Expect(warnings).To(BeEmpty())
   417  			} else {
   418  				warnings, err := webhook.ValidateCreate(ctx, md)
   419  				g.Expect(err).ToNot(HaveOccurred())
   420  				g.Expect(warnings).To(BeEmpty())
   421  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   422  				g.Expect(err).ToNot(HaveOccurred())
   423  				g.Expect(warnings).To(BeEmpty())
   424  			}
   425  		})
   426  	}
   427  }
   428  
   429  func TestMachineDeploymentVersionValidation(t *testing.T) {
   430  	tests := []struct {
   431  		name      string
   432  		version   string
   433  		expectErr bool
   434  	}{
   435  		{
   436  			name:      "should succeed when given a valid semantic version with prepended 'v'",
   437  			version:   "v1.17.2",
   438  			expectErr: false,
   439  		},
   440  		{
   441  			name:      "should return error when given a valid semantic version without 'v'",
   442  			version:   "1.17.2",
   443  			expectErr: true,
   444  		},
   445  		{
   446  			name:      "should return error when given an invalid semantic version",
   447  			version:   "1",
   448  			expectErr: true,
   449  		},
   450  		{
   451  			name:      "should return error when given an invalid semantic version",
   452  			version:   "v1",
   453  			expectErr: true,
   454  		},
   455  		{
   456  			name:      "should return error when given an invalid semantic version",
   457  			version:   "wrong_version",
   458  			expectErr: true,
   459  		},
   460  	}
   461  
   462  	for _, tt := range tests {
   463  		t.Run(tt.name, func(t *testing.T) {
   464  			g := NewWithT(t)
   465  
   466  			md := &clusterv1.MachineDeployment{
   467  				Spec: clusterv1.MachineDeploymentSpec{
   468  					Template: clusterv1.MachineTemplateSpec{
   469  						Spec: clusterv1.MachineSpec{
   470  							Version: ptr.To(tt.version),
   471  						},
   472  					},
   473  				},
   474  			}
   475  
   476  			scheme := runtime.NewScheme()
   477  			g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
   478  			webhook := MachineDeployment{
   479  				decoder: admission.NewDecoder(scheme),
   480  			}
   481  
   482  			if tt.expectErr {
   483  				warnings, err := webhook.ValidateCreate(ctx, md)
   484  				g.Expect(err).To(HaveOccurred())
   485  				g.Expect(warnings).To(BeEmpty())
   486  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   487  				g.Expect(err).To(HaveOccurred())
   488  				g.Expect(warnings).To(BeEmpty())
   489  			} else {
   490  				warnings, err := webhook.ValidateCreate(ctx, md)
   491  				g.Expect(err).ToNot(HaveOccurred())
   492  				g.Expect(warnings).To(BeEmpty())
   493  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   494  				g.Expect(err).ToNot(HaveOccurred())
   495  				g.Expect(warnings).To(BeEmpty())
   496  			}
   497  		})
   498  	}
   499  }
   500  
   501  func TestMachineDeploymentClusterNameImmutable(t *testing.T) {
   502  	tests := []struct {
   503  		name           string
   504  		oldClusterName string
   505  		newClusterName string
   506  		expectErr      bool
   507  	}{
   508  		{
   509  			name:           "when the cluster name has not changed",
   510  			oldClusterName: "foo",
   511  			newClusterName: "foo",
   512  			expectErr:      false,
   513  		},
   514  		{
   515  			name:           "when the cluster name has changed",
   516  			oldClusterName: "foo",
   517  			newClusterName: "bar",
   518  			expectErr:      true,
   519  		},
   520  	}
   521  
   522  	for _, tt := range tests {
   523  		t.Run(tt.name, func(t *testing.T) {
   524  			g := NewWithT(t)
   525  
   526  			newMD := &clusterv1.MachineDeployment{
   527  				Spec: clusterv1.MachineDeploymentSpec{
   528  					ClusterName: tt.newClusterName,
   529  				},
   530  			}
   531  
   532  			oldMD := &clusterv1.MachineDeployment{
   533  				Spec: clusterv1.MachineDeploymentSpec{
   534  					ClusterName: tt.oldClusterName,
   535  				},
   536  			}
   537  
   538  			scheme := runtime.NewScheme()
   539  			g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
   540  			webhook := MachineDeployment{
   541  				decoder: admission.NewDecoder(scheme),
   542  			}
   543  
   544  			warnings, err := webhook.ValidateUpdate(ctx, oldMD, newMD)
   545  			if tt.expectErr {
   546  				g.Expect(err).To(HaveOccurred())
   547  			} else {
   548  				g.Expect(err).ToNot(HaveOccurred())
   549  			}
   550  			g.Expect(warnings).To(BeEmpty())
   551  		})
   552  	}
   553  }
   554  
   555  func TestMachineDeploymentTemplateMetadataValidation(t *testing.T) {
   556  	tests := []struct {
   557  		name        string
   558  		labels      map[string]string
   559  		annotations map[string]string
   560  		expectErr   bool
   561  	}{
   562  		{
   563  			name: "should return error for invalid labels and annotations",
   564  			labels: map[string]string{
   565  				"foo":          "$invalid-key",
   566  				"bar":          strings.Repeat("a", 64) + "too-long-value",
   567  				"/invalid-key": "foo",
   568  			},
   569  			annotations: map[string]string{
   570  				"/invalid-key": "foo",
   571  			},
   572  			expectErr: true,
   573  		},
   574  	}
   575  
   576  	for _, tt := range tests {
   577  		t.Run(tt.name, func(t *testing.T) {
   578  			g := NewWithT(t)
   579  			md := &clusterv1.MachineDeployment{
   580  				Spec: clusterv1.MachineDeploymentSpec{
   581  					Template: clusterv1.MachineTemplateSpec{
   582  						ObjectMeta: clusterv1.ObjectMeta{
   583  							Labels:      tt.labels,
   584  							Annotations: tt.annotations,
   585  						},
   586  					},
   587  				},
   588  			}
   589  
   590  			scheme := runtime.NewScheme()
   591  			g.Expect(clusterv1.AddToScheme(scheme)).To(Succeed())
   592  			webhook := MachineDeployment{
   593  				decoder: admission.NewDecoder(scheme),
   594  			}
   595  
   596  			if tt.expectErr {
   597  				warnings, err := webhook.ValidateCreate(ctx, md)
   598  				g.Expect(err).To(HaveOccurred())
   599  				g.Expect(warnings).To(BeEmpty())
   600  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   601  				g.Expect(err).To(HaveOccurred())
   602  				g.Expect(warnings).To(BeEmpty())
   603  			} else {
   604  				warnings, err := webhook.ValidateCreate(ctx, md)
   605  				g.Expect(err).ToNot(HaveOccurred())
   606  				g.Expect(warnings).To(BeEmpty())
   607  				warnings, err = webhook.ValidateUpdate(ctx, md, md)
   608  				g.Expect(err).ToNot(HaveOccurred())
   609  				g.Expect(warnings).To(BeEmpty())
   610  			}
   611  		})
   612  	}
   613  }