sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/machinedeployment_rolling_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 machinedeployment
    18  
    19  import (
    20  	"strconv"
    21  	"testing"
    22  
    23  	. "github.com/onsi/gomega"
    24  	"github.com/pkg/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/client-go/tools/record"
    27  	"k8s.io/utils/ptr"
    28  	"sigs.k8s.io/controller-runtime/pkg/client"
    29  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    30  
    31  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    32  	"sigs.k8s.io/cluster-api/internal/controllers/machinedeployment/mdutil"
    33  )
    34  
    35  func TestReconcileNewMachineSet(t *testing.T) {
    36  	testCases := []struct {
    37  		name                          string
    38  		machineDeployment             *clusterv1.MachineDeployment
    39  		newMachineSet                 *clusterv1.MachineSet
    40  		oldMachineSets                []*clusterv1.MachineSet
    41  		expectedNewMachineSetReplicas int
    42  		error                         error
    43  	}{
    44  		{
    45  			name: "It fails when machineDeployment has no replicas",
    46  			machineDeployment: &clusterv1.MachineDeployment{
    47  				ObjectMeta: metav1.ObjectMeta{
    48  					Namespace: "foo",
    49  					Name:      "bar",
    50  				},
    51  			},
    52  			newMachineSet: &clusterv1.MachineSet{
    53  				Spec: clusterv1.MachineSetSpec{
    54  					Replicas: ptr.To[int32](2),
    55  				},
    56  			},
    57  			error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"),
    58  		},
    59  		{
    60  			name: "It fails when new machineSet has no replicas",
    61  			machineDeployment: &clusterv1.MachineDeployment{
    62  				Spec: clusterv1.MachineDeploymentSpec{
    63  					Replicas: ptr.To[int32](2),
    64  				},
    65  			},
    66  			newMachineSet: &clusterv1.MachineSet{
    67  				ObjectMeta: metav1.ObjectMeta{
    68  					Namespace: "foo",
    69  					Name:      "bar",
    70  				},
    71  			},
    72  			error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"),
    73  		},
    74  		{
    75  			name: "RollingUpdate strategy: Scale up: 0 -> 2",
    76  			machineDeployment: &clusterv1.MachineDeployment{
    77  				ObjectMeta: metav1.ObjectMeta{
    78  					Namespace: "foo",
    79  					Name:      "bar",
    80  				},
    81  				Spec: clusterv1.MachineDeploymentSpec{
    82  					Strategy: &clusterv1.MachineDeploymentStrategy{
    83  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
    84  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
    85  							MaxUnavailable: intOrStrPtr(0),
    86  							MaxSurge:       intOrStrPtr(2),
    87  						},
    88  					},
    89  					Replicas: ptr.To[int32](2),
    90  				},
    91  			},
    92  			newMachineSet: &clusterv1.MachineSet{
    93  				ObjectMeta: metav1.ObjectMeta{
    94  					Namespace: "foo",
    95  					Name:      "bar",
    96  				},
    97  				Spec: clusterv1.MachineSetSpec{
    98  					Replicas: ptr.To[int32](0),
    99  				},
   100  			},
   101  			expectedNewMachineSetReplicas: 2,
   102  		},
   103  		{
   104  			name: "RollingUpdate strategy: Scale down: 2 -> 0",
   105  			machineDeployment: &clusterv1.MachineDeployment{
   106  				ObjectMeta: metav1.ObjectMeta{
   107  					Namespace: "foo",
   108  					Name:      "bar",
   109  				},
   110  				Spec: clusterv1.MachineDeploymentSpec{
   111  					Strategy: &clusterv1.MachineDeploymentStrategy{
   112  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   113  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   114  							MaxUnavailable: intOrStrPtr(0),
   115  							MaxSurge:       intOrStrPtr(2),
   116  						},
   117  					},
   118  					Replicas: ptr.To[int32](0),
   119  				},
   120  			},
   121  			newMachineSet: &clusterv1.MachineSet{
   122  				ObjectMeta: metav1.ObjectMeta{
   123  					Namespace: "foo",
   124  					Name:      "bar",
   125  				},
   126  				Spec: clusterv1.MachineSetSpec{
   127  					Replicas: ptr.To[int32](2),
   128  				},
   129  			},
   130  			expectedNewMachineSetReplicas: 0,
   131  		},
   132  		{
   133  			name: "RollingUpdate strategy: Scale up does not go above maxSurge (3+2)",
   134  			machineDeployment: &clusterv1.MachineDeployment{
   135  				ObjectMeta: metav1.ObjectMeta{
   136  					Namespace: "foo",
   137  					Name:      "bar",
   138  				},
   139  				Spec: clusterv1.MachineDeploymentSpec{
   140  					Strategy: &clusterv1.MachineDeploymentStrategy{
   141  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   142  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   143  							MaxUnavailable: intOrStrPtr(0),
   144  							MaxSurge:       intOrStrPtr(2),
   145  						},
   146  					},
   147  					Replicas: ptr.To[int32](3),
   148  				},
   149  			},
   150  			newMachineSet: &clusterv1.MachineSet{
   151  				ObjectMeta: metav1.ObjectMeta{
   152  					Namespace: "foo",
   153  					Name:      "bar",
   154  				},
   155  				Spec: clusterv1.MachineSetSpec{
   156  					Replicas: ptr.To[int32](1),
   157  				},
   158  			},
   159  			expectedNewMachineSetReplicas: 2,
   160  			oldMachineSets: []*clusterv1.MachineSet{
   161  				{
   162  					ObjectMeta: metav1.ObjectMeta{
   163  						Namespace: "foo",
   164  						Name:      "3replicas",
   165  					},
   166  					Spec: clusterv1.MachineSetSpec{
   167  						Replicas: ptr.To[int32](3),
   168  					},
   169  					Status: clusterv1.MachineSetStatus{
   170  						Replicas: 3,
   171  					},
   172  				},
   173  			},
   174  			error: nil,
   175  		},
   176  		{
   177  			name: "RollingUpdate strategy: Scale up accounts for deleting Machines to honour maxSurge",
   178  			machineDeployment: &clusterv1.MachineDeployment{
   179  				ObjectMeta: metav1.ObjectMeta{
   180  					Namespace: "foo",
   181  					Name:      "bar",
   182  				},
   183  				Spec: clusterv1.MachineDeploymentSpec{
   184  					Strategy: &clusterv1.MachineDeploymentStrategy{
   185  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   186  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   187  							MaxUnavailable: intOrStrPtr(0),
   188  							MaxSurge:       intOrStrPtr(0),
   189  						},
   190  					},
   191  					Replicas: ptr.To[int32](1),
   192  				},
   193  			},
   194  			newMachineSet: &clusterv1.MachineSet{
   195  				ObjectMeta: metav1.ObjectMeta{
   196  					Namespace: "foo",
   197  					Name:      "bar",
   198  				},
   199  				Spec: clusterv1.MachineSetSpec{
   200  					Replicas: ptr.To[int32](0),
   201  				},
   202  			},
   203  			expectedNewMachineSetReplicas: 0,
   204  			oldMachineSets: []*clusterv1.MachineSet{
   205  				{
   206  					ObjectMeta: metav1.ObjectMeta{
   207  						Namespace: "foo",
   208  						Name:      "machine-not-yet-deleted",
   209  					},
   210  					Spec: clusterv1.MachineSetSpec{
   211  						Replicas: ptr.To[int32](0),
   212  					},
   213  					Status: clusterv1.MachineSetStatus{
   214  						Replicas: 1,
   215  					},
   216  				},
   217  			},
   218  			error: nil,
   219  		},
   220  	}
   221  
   222  	for _, tc := range testCases {
   223  		t.Run(tc.name, func(t *testing.T) {
   224  			g := NewWithT(t)
   225  
   226  			resources := []client.Object{
   227  				tc.machineDeployment,
   228  			}
   229  
   230  			allMachineSets := append(tc.oldMachineSets, tc.newMachineSet)
   231  			for key := range allMachineSets {
   232  				resources = append(resources, allMachineSets[key])
   233  			}
   234  
   235  			r := &Reconciler{
   236  				Client:   fake.NewClientBuilder().WithObjects(resources...).Build(),
   237  				recorder: record.NewFakeRecorder(32),
   238  			}
   239  
   240  			err := r.reconcileNewMachineSet(ctx, allMachineSets, tc.newMachineSet, tc.machineDeployment)
   241  			if tc.error != nil {
   242  				g.Expect(err).To(HaveOccurred())
   243  				g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error()))
   244  				return
   245  			}
   246  
   247  			g.Expect(err).ToNot(HaveOccurred())
   248  
   249  			freshNewMachineSet := &clusterv1.MachineSet{}
   250  			err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.newMachineSet), freshNewMachineSet)
   251  			g.Expect(err).ToNot(HaveOccurred())
   252  
   253  			g.Expect(*freshNewMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedNewMachineSetReplicas))
   254  
   255  			desiredReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.DesiredReplicasAnnotation]
   256  			g.Expect(ok).To(BeTrue())
   257  			g.Expect(strconv.Atoi(desiredReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas))
   258  
   259  			maxReplicasAnnotation, ok := freshNewMachineSet.GetAnnotations()[clusterv1.MaxReplicasAnnotation]
   260  			g.Expect(ok).To(BeTrue())
   261  			g.Expect(strconv.Atoi(maxReplicasAnnotation)).To(BeEquivalentTo(*tc.machineDeployment.Spec.Replicas + mdutil.MaxSurge(*tc.machineDeployment)))
   262  		})
   263  	}
   264  }
   265  
   266  func TestReconcileOldMachineSets(t *testing.T) {
   267  	testCases := []struct {
   268  		name                           string
   269  		machineDeployment              *clusterv1.MachineDeployment
   270  		newMachineSet                  *clusterv1.MachineSet
   271  		oldMachineSets                 []*clusterv1.MachineSet
   272  		expectedOldMachineSetsReplicas int
   273  		error                          error
   274  	}{
   275  		{
   276  			name: "It fails when machineDeployment has no replicas",
   277  			machineDeployment: &clusterv1.MachineDeployment{
   278  				ObjectMeta: metav1.ObjectMeta{
   279  					Namespace: "foo",
   280  					Name:      "bar",
   281  				},
   282  			},
   283  			newMachineSet: &clusterv1.MachineSet{
   284  				Spec: clusterv1.MachineSetSpec{
   285  					Replicas: ptr.To[int32](2),
   286  				},
   287  			},
   288  			error: errors.Errorf("spec.replicas for MachineDeployment foo/bar is nil, this is unexpected"),
   289  		},
   290  		{
   291  			name: "It fails when new machineSet has no replicas",
   292  			machineDeployment: &clusterv1.MachineDeployment{
   293  				Spec: clusterv1.MachineDeploymentSpec{
   294  					Replicas: ptr.To[int32](2),
   295  				},
   296  			},
   297  			newMachineSet: &clusterv1.MachineSet{
   298  				ObjectMeta: metav1.ObjectMeta{
   299  					Namespace: "foo",
   300  					Name:      "bar",
   301  				},
   302  			},
   303  			error: errors.Errorf("spec.replicas for MachineSet foo/bar is nil, this is unexpected"),
   304  		},
   305  		{
   306  			name: "RollingUpdate strategy: Scale down old MachineSets when all new replicas are available",
   307  			machineDeployment: &clusterv1.MachineDeployment{
   308  				ObjectMeta: metav1.ObjectMeta{
   309  					Namespace: "foo",
   310  					Name:      "bar",
   311  				},
   312  				Spec: clusterv1.MachineDeploymentSpec{
   313  					Strategy: &clusterv1.MachineDeploymentStrategy{
   314  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   315  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   316  							MaxUnavailable: intOrStrPtr(1),
   317  							MaxSurge:       intOrStrPtr(3),
   318  						},
   319  					},
   320  					Replicas: ptr.To[int32](2),
   321  				},
   322  			},
   323  			newMachineSet: &clusterv1.MachineSet{
   324  				ObjectMeta: metav1.ObjectMeta{
   325  					Namespace: "foo",
   326  					Name:      "bar",
   327  				},
   328  				Spec: clusterv1.MachineSetSpec{
   329  					Replicas: ptr.To[int32](0),
   330  				},
   331  				Status: clusterv1.MachineSetStatus{
   332  					AvailableReplicas: 2,
   333  				},
   334  			},
   335  			oldMachineSets: []*clusterv1.MachineSet{
   336  				{
   337  					ObjectMeta: metav1.ObjectMeta{
   338  						Namespace: "foo",
   339  						Name:      "2replicas",
   340  					},
   341  					Spec: clusterv1.MachineSetSpec{
   342  						Replicas: ptr.To[int32](2),
   343  					},
   344  					Status: clusterv1.MachineSetStatus{
   345  						AvailableReplicas: 2,
   346  					},
   347  				},
   348  				{
   349  					ObjectMeta: metav1.ObjectMeta{
   350  						Namespace: "foo",
   351  						Name:      "1replicas",
   352  					},
   353  					Spec: clusterv1.MachineSetSpec{
   354  						Replicas: ptr.To[int32](1),
   355  					},
   356  					Status: clusterv1.MachineSetStatus{
   357  						AvailableReplicas: 1,
   358  					},
   359  				},
   360  			},
   361  			expectedOldMachineSetsReplicas: 0,
   362  		},
   363  		{
   364  			name: "RollingUpdate strategy: It does not scale down old MachineSets when above maxUnavailable",
   365  			machineDeployment: &clusterv1.MachineDeployment{
   366  				ObjectMeta: metav1.ObjectMeta{
   367  					Namespace: "foo",
   368  					Name:      "bar",
   369  				},
   370  				Spec: clusterv1.MachineDeploymentSpec{
   371  					Strategy: &clusterv1.MachineDeploymentStrategy{
   372  						Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   373  						RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   374  							MaxUnavailable: intOrStrPtr(2),
   375  							MaxSurge:       intOrStrPtr(3),
   376  						},
   377  					},
   378  					Replicas: ptr.To[int32](10),
   379  				},
   380  			},
   381  			newMachineSet: &clusterv1.MachineSet{
   382  				ObjectMeta: metav1.ObjectMeta{
   383  					Namespace: "foo",
   384  					Name:      "bar",
   385  				},
   386  				Spec: clusterv1.MachineSetSpec{
   387  					Replicas: ptr.To[int32](5),
   388  				},
   389  				Status: clusterv1.MachineSetStatus{
   390  					Replicas:          5,
   391  					ReadyReplicas:     0,
   392  					AvailableReplicas: 0,
   393  				},
   394  			},
   395  			oldMachineSets: []*clusterv1.MachineSet{
   396  				{
   397  					ObjectMeta: metav1.ObjectMeta{
   398  						Namespace: "foo",
   399  						Name:      "8replicas",
   400  					},
   401  					Spec: clusterv1.MachineSetSpec{
   402  						Replicas: ptr.To[int32](8),
   403  					},
   404  					Status: clusterv1.MachineSetStatus{
   405  						Replicas:          10,
   406  						ReadyReplicas:     8,
   407  						AvailableReplicas: 8,
   408  					},
   409  				},
   410  			},
   411  			expectedOldMachineSetsReplicas: 8,
   412  		},
   413  	}
   414  	for _, tc := range testCases {
   415  		t.Run(tc.name, func(t *testing.T) {
   416  			g := NewWithT(t)
   417  
   418  			resources := []client.Object{
   419  				tc.machineDeployment,
   420  			}
   421  
   422  			allMachineSets := append(tc.oldMachineSets, tc.newMachineSet)
   423  			for key := range allMachineSets {
   424  				resources = append(resources, allMachineSets[key])
   425  			}
   426  
   427  			r := &Reconciler{
   428  				Client:   fake.NewClientBuilder().WithObjects(resources...).Build(),
   429  				recorder: record.NewFakeRecorder(32),
   430  			}
   431  
   432  			err := r.reconcileOldMachineSets(ctx, allMachineSets, tc.oldMachineSets, tc.newMachineSet, tc.machineDeployment)
   433  			if tc.error != nil {
   434  				g.Expect(err).To(HaveOccurred())
   435  				g.Expect(err.Error()).To(BeEquivalentTo(tc.error.Error()))
   436  				return
   437  			}
   438  
   439  			g.Expect(err).ToNot(HaveOccurred())
   440  			for key := range tc.oldMachineSets {
   441  				freshOldMachineSet := &clusterv1.MachineSet{}
   442  				err = r.Client.Get(ctx, client.ObjectKeyFromObject(tc.oldMachineSets[key]), freshOldMachineSet)
   443  				g.Expect(err).ToNot(HaveOccurred())
   444  				g.Expect(*freshOldMachineSet.Spec.Replicas).To(BeEquivalentTo(tc.expectedOldMachineSetsReplicas))
   445  			}
   446  		})
   447  	}
   448  }