sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/webhooks/scale_test.go (about)

     1  /*
     2  Copyright 2020 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  	"testing"
    22  	"time"
    23  
    24  	. "github.com/onsi/gomega"
    25  	admissionv1 "k8s.io/api/admission/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/intstr"
    30  	"k8s.io/apimachinery/pkg/util/uuid"
    31  	"k8s.io/utils/ptr"
    32  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    33  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    34  
    35  	bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1"
    36  	controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
    37  )
    38  
    39  func init() {
    40  	scheme = runtime.NewScheme()
    41  	_ = controlplanev1.AddToScheme(scheme)
    42  	_ = admissionv1.AddToScheme(scheme)
    43  }
    44  
    45  var (
    46  	scheme *runtime.Scheme
    47  )
    48  
    49  func TestKubeadmControlPlaneValidateScale(t *testing.T) {
    50  	kcpManagedEtcd := &controlplanev1.KubeadmControlPlane{
    51  		ObjectMeta: metav1.ObjectMeta{
    52  			Name:      "kcp-managed-etcd",
    53  			Namespace: "foo",
    54  		},
    55  		Spec: controlplanev1.KubeadmControlPlaneSpec{
    56  			MachineTemplate: controlplanev1.KubeadmControlPlaneMachineTemplate{
    57  				InfrastructureRef: corev1.ObjectReference{
    58  					APIVersion: "test/v1alpha1",
    59  					Kind:       "UnknownInfraMachine",
    60  					Namespace:  "foo",
    61  					Name:       "infraTemplate",
    62  				},
    63  				NodeDrainTimeout: &metav1.Duration{Duration: time.Second},
    64  			},
    65  			Replicas: ptr.To[int32](1),
    66  			RolloutStrategy: &controlplanev1.RolloutStrategy{
    67  				Type: controlplanev1.RollingUpdateStrategyType,
    68  				RollingUpdate: &controlplanev1.RollingUpdate{
    69  					MaxSurge: &intstr.IntOrString{
    70  						IntVal: 1,
    71  					},
    72  				},
    73  			},
    74  			KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{
    75  				InitConfiguration: &bootstrapv1.InitConfiguration{
    76  					LocalAPIEndpoint: bootstrapv1.APIEndpoint{
    77  						AdvertiseAddress: "127.0.0.1",
    78  						BindPort:         int32(443),
    79  					},
    80  					NodeRegistration: bootstrapv1.NodeRegistrationOptions{
    81  						Name: "kcp-managed-etcd",
    82  					},
    83  				},
    84  				ClusterConfiguration: &bootstrapv1.ClusterConfiguration{
    85  					ClusterName: "kcp-managed-etcd",
    86  					DNS: bootstrapv1.DNS{
    87  						ImageMeta: bootstrapv1.ImageMeta{
    88  							ImageRepository: "registry.k8s.io/coredns",
    89  							ImageTag:        "1.6.5",
    90  						},
    91  					},
    92  				},
    93  				JoinConfiguration: &bootstrapv1.JoinConfiguration{
    94  					Discovery: bootstrapv1.Discovery{
    95  						Timeout: &metav1.Duration{
    96  							Duration: 10 * time.Minute,
    97  						},
    98  					},
    99  					NodeRegistration: bootstrapv1.NodeRegistrationOptions{
   100  						Name: "kcp-managed-etcd",
   101  					},
   102  				},
   103  				PreKubeadmCommands: []string{
   104  					"kcp-managed-etcd", "foo",
   105  				},
   106  				PostKubeadmCommands: []string{
   107  					"kcp-managed-etcd", "foo",
   108  				},
   109  				Files: []bootstrapv1.File{
   110  					{
   111  						Path: "kcp-managed-etcd",
   112  					},
   113  				},
   114  				Users: []bootstrapv1.User{
   115  					{
   116  						Name: "user",
   117  						SSHAuthorizedKeys: []string{
   118  							"ssh-rsa foo",
   119  						},
   120  					},
   121  				},
   122  				NTP: &bootstrapv1.NTP{
   123  					Servers: []string{"test-server-1", "test-server-2"},
   124  					Enabled: ptr.To(true),
   125  				},
   126  			},
   127  			Version: "v1.16.6",
   128  		},
   129  	}
   130  
   131  	kcpExternalEtcd := kcpManagedEtcd.DeepCopy()
   132  	kcpExternalEtcd.ObjectMeta.Name = "kcp-external-etcd"
   133  	kcpExternalEtcd.Spec.KubeadmConfigSpec.ClusterConfiguration.Etcd.External = &bootstrapv1.ExternalEtcd{}
   134  
   135  	tests := []struct {
   136  		name              string
   137  		admissionRequest  admission.Request
   138  		expectRespAllowed bool
   139  		expectRespMessage string
   140  	}{
   141  		{
   142  			name:              "should return error when trying to scale to zero",
   143  			expectRespAllowed: false,
   144  			expectRespMessage: "replicas cannot be 0",
   145  			admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
   146  				UID:       uuid.NewUUID(),
   147  				Kind:      metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"},
   148  				Operation: admissionv1.Update,
   149  				Object:    runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":0}}`)},
   150  			}},
   151  		},
   152  		{
   153  			name:              "should return error when trying to scale to even number of replicas with managed etcd",
   154  			expectRespAllowed: false,
   155  			expectRespMessage: "replicas cannot be an even number when etcd is stacked",
   156  			admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
   157  				UID:       uuid.NewUUID(),
   158  				Kind:      metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"},
   159  				Operation: admissionv1.Update,
   160  				Object:    runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":2}}`)},
   161  			}},
   162  		},
   163  		{
   164  			name:              "should allow odd number of replicas with managed etcd",
   165  			expectRespAllowed: true,
   166  			expectRespMessage: "",
   167  			admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
   168  				UID:       uuid.NewUUID(),
   169  				Kind:      metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"},
   170  				Operation: admissionv1.Update,
   171  				Object:    runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-managed-etcd","namespace":"foo"},"spec":{"replicas":3}}`)},
   172  			}},
   173  		},
   174  		{
   175  			name:              "should allow even number of replicas with external etcd",
   176  			expectRespAllowed: true,
   177  			expectRespMessage: "",
   178  			admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
   179  				UID:       uuid.NewUUID(),
   180  				Kind:      metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"},
   181  				Operation: admissionv1.Update,
   182  				Object:    runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-external-etcd","namespace":"foo"},"spec":{"replicas":4}}`)},
   183  			}},
   184  		},
   185  		{
   186  			name:              "should allow odd number of replicas with external etcd",
   187  			expectRespAllowed: true,
   188  			expectRespMessage: "",
   189  			admissionRequest: admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{
   190  				UID:       uuid.NewUUID(),
   191  				Kind:      metav1.GroupVersionKind{Group: "autoscaling", Version: "v1", Kind: "Scale"},
   192  				Operation: admissionv1.Update,
   193  				Object:    runtime.RawExtension{Raw: []byte(`{"metadata":{"name":"kcp-external-etcd","namespace":"foo"},"spec":{"replicas":3}}`)},
   194  			}},
   195  		},
   196  	}
   197  	for _, tt := range tests {
   198  		t.Run(tt.name, func(t *testing.T) {
   199  			g := NewWithT(t)
   200  
   201  			fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(kcpManagedEtcd, kcpExternalEtcd).Build()
   202  
   203  			// Create the webhook and add the fakeClient as its client.
   204  			scaleHandler := ScaleValidator{
   205  				Client:  fakeClient,
   206  				decoder: admission.NewDecoder(scheme),
   207  			}
   208  
   209  			resp := scaleHandler.Handle(context.Background(), tt.admissionRequest)
   210  			g.Expect(resp.Allowed).Should(Equal(tt.expectRespAllowed))
   211  			g.Expect(resp.Result.Message).Should(Equal(tt.expectRespMessage))
   212  		})
   213  	}
   214  }