sigs.k8s.io/cluster-api@v1.7.1/internal/controllers/machinedeployment/mdutil/util_test.go (about)

     1  /*
     2  Copyright 2018 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 mdutil
    18  
    19  import (
    20  	"fmt"
    21  	"math/rand"
    22  	"sort"
    23  	"strconv"
    24  	"testing"
    25  	"time"
    26  
    27  	. "github.com/onsi/gomega"
    28  	corev1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  	"k8s.io/apimachinery/pkg/util/intstr"
    32  	"k8s.io/apiserver/pkg/storage/names"
    33  	"k8s.io/utils/ptr"
    34  	ctrl "sigs.k8s.io/controller-runtime"
    35  
    36  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    37  )
    38  
    39  var (
    40  	ctx = ctrl.SetupSignalHandler()
    41  )
    42  
    43  func newDControllerRef(md *clusterv1.MachineDeployment) *metav1.OwnerReference {
    44  	isController := true
    45  	return &metav1.OwnerReference{
    46  		APIVersion: "clusters/v1alpha",
    47  		Kind:       "MachineDeployment",
    48  		Name:       md.GetName(),
    49  		UID:        md.GetUID(),
    50  		Controller: &isController,
    51  	}
    52  }
    53  
    54  // generateMS creates a machine set, with the input deployment's template as its template.
    55  func generateMS(md clusterv1.MachineDeployment) clusterv1.MachineSet {
    56  	template := md.Spec.Template.DeepCopy()
    57  	return clusterv1.MachineSet{
    58  		ObjectMeta: metav1.ObjectMeta{
    59  			UID:             randomUID(),
    60  			Name:            names.SimpleNameGenerator.GenerateName("machineset"),
    61  			Labels:          template.Labels,
    62  			OwnerReferences: []metav1.OwnerReference{*newDControllerRef(&md)},
    63  		},
    64  		Spec: clusterv1.MachineSetSpec{
    65  			Replicas: new(int32),
    66  			Template: *template,
    67  			Selector: metav1.LabelSelector{MatchLabels: template.Labels},
    68  		},
    69  	}
    70  }
    71  
    72  func randomUID() types.UID {
    73  	return types.UID(strconv.FormatInt(rand.Int63(), 10)) //nolint:gosec
    74  }
    75  
    76  // generateDeployment creates a deployment, with the input image as its template.
    77  func generateDeployment(image string) clusterv1.MachineDeployment {
    78  	machineLabels := map[string]string{"name": image}
    79  	return clusterv1.MachineDeployment{
    80  		ObjectMeta: metav1.ObjectMeta{
    81  			Name:        image,
    82  			Annotations: make(map[string]string),
    83  		},
    84  		Spec: clusterv1.MachineDeploymentSpec{
    85  			Replicas: ptr.To[int32](3),
    86  			Selector: metav1.LabelSelector{MatchLabels: machineLabels},
    87  			Template: clusterv1.MachineTemplateSpec{
    88  				ObjectMeta: clusterv1.ObjectMeta{
    89  					Labels: machineLabels,
    90  				},
    91  				Spec: clusterv1.MachineSpec{
    92  					NodeDrainTimeout: &metav1.Duration{Duration: 10 * time.Second},
    93  				},
    94  			},
    95  		},
    96  	}
    97  }
    98  
    99  func TestMachineSetsByDecreasingReplicas(t *testing.T) {
   100  	t0 := time.Now()
   101  	t1 := t0.Add(1 * time.Minute)
   102  	msAReplicas1T0 := &clusterv1.MachineSet{
   103  		ObjectMeta: metav1.ObjectMeta{
   104  			CreationTimestamp: metav1.Time{Time: t0},
   105  			Name:              "ms-a",
   106  		},
   107  		Spec: clusterv1.MachineSetSpec{
   108  			Replicas: ptr.To[int32](1),
   109  		},
   110  	}
   111  
   112  	msAAReplicas3T0 := &clusterv1.MachineSet{
   113  		ObjectMeta: metav1.ObjectMeta{
   114  			CreationTimestamp: metav1.Time{Time: t0},
   115  			Name:              "ms-aa",
   116  		},
   117  		Spec: clusterv1.MachineSetSpec{
   118  			Replicas: ptr.To[int32](3),
   119  		},
   120  	}
   121  
   122  	msBReplicas1T0 := &clusterv1.MachineSet{
   123  		ObjectMeta: metav1.ObjectMeta{
   124  			CreationTimestamp: metav1.Time{Time: t0},
   125  			Name:              "ms-b",
   126  		},
   127  		Spec: clusterv1.MachineSetSpec{
   128  			Replicas: ptr.To[int32](1),
   129  		},
   130  	}
   131  
   132  	msAReplicas1T1 := &clusterv1.MachineSet{
   133  		ObjectMeta: metav1.ObjectMeta{
   134  			CreationTimestamp: metav1.Time{Time: t1},
   135  			Name:              "ms-a",
   136  		},
   137  		Spec: clusterv1.MachineSetSpec{
   138  			Replicas: ptr.To[int32](1),
   139  		},
   140  	}
   141  
   142  	tests := []struct {
   143  		name        string
   144  		machineSets []*clusterv1.MachineSet
   145  		want        []*clusterv1.MachineSet
   146  	}{
   147  		{
   148  			name:        "machine set with higher replicas should be lower in the list",
   149  			machineSets: []*clusterv1.MachineSet{msAReplicas1T0, msAAReplicas3T0},
   150  			want:        []*clusterv1.MachineSet{msAAReplicas3T0, msAReplicas1T0},
   151  		},
   152  		{
   153  			name:        "MachineSet created earlier should be lower in the list if replicas are the same",
   154  			machineSets: []*clusterv1.MachineSet{msAReplicas1T1, msAReplicas1T0},
   155  			want:        []*clusterv1.MachineSet{msAReplicas1T0, msAReplicas1T1},
   156  		},
   157  		{
   158  			name:        "MachineSet with lower name should be lower in the list if the replicas and creationTimestamp are same",
   159  			machineSets: []*clusterv1.MachineSet{msBReplicas1T0, msAReplicas1T0},
   160  			want:        []*clusterv1.MachineSet{msAReplicas1T0, msBReplicas1T0},
   161  		},
   162  	}
   163  
   164  	for _, tt := range tests {
   165  		t.Run(tt.name, func(t *testing.T) {
   166  			// sort the machine sets and verify the sorted list
   167  			g := NewWithT(t)
   168  			sort.Sort(MachineSetsByDecreasingReplicas(tt.machineSets))
   169  			g.Expect(tt.machineSets).To(BeComparableTo(tt.want))
   170  		})
   171  	}
   172  }
   173  
   174  func TestEqualMachineTemplate(t *testing.T) {
   175  	machineTemplate := &clusterv1.MachineTemplateSpec{
   176  		ObjectMeta: clusterv1.ObjectMeta{
   177  			Labels:      map[string]string{"l1": "v1"},
   178  			Annotations: map[string]string{"a1": "v1"},
   179  		},
   180  		Spec: clusterv1.MachineSpec{
   181  			NodeDrainTimeout:        &metav1.Duration{Duration: 10 * time.Second},
   182  			NodeDeletionTimeout:     &metav1.Duration{Duration: 10 * time.Second},
   183  			NodeVolumeDetachTimeout: &metav1.Duration{Duration: 10 * time.Second},
   184  			InfrastructureRef: corev1.ObjectReference{
   185  				Name:       "infra1",
   186  				Namespace:  "default",
   187  				Kind:       "InfrastructureMachine",
   188  				APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1",
   189  			},
   190  			Bootstrap: clusterv1.Bootstrap{
   191  				ConfigRef: &corev1.ObjectReference{
   192  					Name:       "bootstrap1",
   193  					Namespace:  "default",
   194  					Kind:       "BootstrapConfig",
   195  					APIVersion: "bootstrap.cluster.x-k8s.io/v1beta1",
   196  				},
   197  			},
   198  		},
   199  	}
   200  
   201  	machineTemplateWithEmptyLabels := machineTemplate.DeepCopy()
   202  	machineTemplateWithEmptyLabels.Labels = map[string]string{}
   203  
   204  	machineTemplateWithDifferentLabels := machineTemplate.DeepCopy()
   205  	machineTemplateWithDifferentLabels.Labels = map[string]string{"l2": "v2"}
   206  
   207  	machineTemplateWithEmptyAnnotations := machineTemplate.DeepCopy()
   208  	machineTemplateWithEmptyAnnotations.Annotations = map[string]string{}
   209  
   210  	machineTemplateWithDifferentAnnotations := machineTemplate.DeepCopy()
   211  	machineTemplateWithDifferentAnnotations.Annotations = map[string]string{"a2": "v2"}
   212  
   213  	machineTemplateWithDifferentInPlaceMutableSpecFields := machineTemplate.DeepCopy()
   214  	machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeDrainTimeout = &metav1.Duration{Duration: 20 * time.Second}
   215  	machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeDeletionTimeout = &metav1.Duration{Duration: 20 * time.Second}
   216  	machineTemplateWithDifferentInPlaceMutableSpecFields.Spec.NodeVolumeDetachTimeout = &metav1.Duration{Duration: 20 * time.Second}
   217  
   218  	machineTemplateWithDifferentInfraRef := machineTemplate.DeepCopy()
   219  	machineTemplateWithDifferentInfraRef.Spec.InfrastructureRef.Name = "infra2"
   220  
   221  	machineTemplateWithDifferentInfraRefAPIVersion := machineTemplate.DeepCopy()
   222  	machineTemplateWithDifferentInfraRefAPIVersion.Spec.InfrastructureRef.APIVersion = "infrastructure.cluster.x-k8s.io/v1beta2"
   223  
   224  	machineTemplateWithDifferentBootstrapConfigRef := machineTemplate.DeepCopy()
   225  	machineTemplateWithDifferentBootstrapConfigRef.Spec.Bootstrap.ConfigRef.Name = "bootstrap2"
   226  
   227  	machineTemplateWithDifferentBootstrapConfigRefAPIVersion := machineTemplate.DeepCopy()
   228  	machineTemplateWithDifferentBootstrapConfigRefAPIVersion.Spec.Bootstrap.ConfigRef.APIVersion = "bootstrap.cluster.x-k8s.io/v1beta2"
   229  
   230  	tests := []struct {
   231  		Name           string
   232  		Former, Latter *clusterv1.MachineTemplateSpec
   233  		Expected       bool
   234  	}{
   235  		{
   236  			Name:     "Same spec, except latter does not have labels",
   237  			Former:   machineTemplate,
   238  			Latter:   machineTemplateWithEmptyLabels,
   239  			Expected: true,
   240  		},
   241  		{
   242  			Name:     "Same spec, except latter has different labels",
   243  			Former:   machineTemplate,
   244  			Latter:   machineTemplateWithDifferentLabels,
   245  			Expected: true,
   246  		},
   247  		{
   248  			Name:     "Same spec, except latter does not have annotations",
   249  			Former:   machineTemplate,
   250  			Latter:   machineTemplateWithEmptyAnnotations,
   251  			Expected: true,
   252  		},
   253  		{
   254  			Name:     "Same spec, except latter has different annotations",
   255  			Former:   machineTemplate,
   256  			Latter:   machineTemplateWithDifferentAnnotations,
   257  			Expected: true,
   258  		},
   259  		{
   260  			Name:     "Spec changes, latter has different in-place mutable spec fields",
   261  			Former:   machineTemplate,
   262  			Latter:   machineTemplateWithDifferentInPlaceMutableSpecFields,
   263  			Expected: true,
   264  		},
   265  		{
   266  			Name:     "Spec changes, latter has different InfrastructureRef",
   267  			Former:   machineTemplate,
   268  			Latter:   machineTemplateWithDifferentInfraRef,
   269  			Expected: false,
   270  		},
   271  		{
   272  			Name:     "Spec changes, latter has different Bootstrap.ConfigRef",
   273  			Former:   machineTemplate,
   274  			Latter:   machineTemplateWithDifferentBootstrapConfigRef,
   275  			Expected: false,
   276  		},
   277  		{
   278  			Name:     "Same spec, except latter has different InfrastructureRef APIVersion",
   279  			Former:   machineTemplate,
   280  			Latter:   machineTemplateWithDifferentInfraRefAPIVersion,
   281  			Expected: true,
   282  		},
   283  		{
   284  			Name:     "Same spec, except latter has different Bootstrap.ConfigRef APIVersion",
   285  			Former:   machineTemplate,
   286  			Latter:   machineTemplateWithDifferentBootstrapConfigRefAPIVersion,
   287  			Expected: true,
   288  		},
   289  	}
   290  
   291  	for _, test := range tests {
   292  		t.Run(test.Name, func(t *testing.T) {
   293  			g := NewWithT(t)
   294  
   295  			runTest := func(t1, t2 *clusterv1.MachineTemplateSpec) {
   296  				// Run
   297  				equal := EqualMachineTemplate(t1, t2)
   298  				g.Expect(equal).To(Equal(test.Expected))
   299  				g.Expect(t1.Labels).NotTo(BeNil())
   300  				g.Expect(t2.Labels).NotTo(BeNil())
   301  			}
   302  
   303  			runTest(test.Former, test.Latter)
   304  			// Test the same case in reverse order
   305  			runTest(test.Latter, test.Former)
   306  		})
   307  	}
   308  }
   309  
   310  func TestFindNewMachineSet(t *testing.T) {
   311  	twoBeforeRolloutAfter := metav1.Now()
   312  	oneBeforeRolloutAfter := metav1.NewTime(twoBeforeRolloutAfter.Add(time.Minute))
   313  	rolloutAfter := metav1.NewTime(oneBeforeRolloutAfter.Add(time.Minute))
   314  	oneAfterRolloutAfter := metav1.NewTime(rolloutAfter.Add(time.Minute))
   315  	twoAfterRolloutAfter := metav1.NewTime(oneAfterRolloutAfter.Add(time.Minute))
   316  
   317  	deployment := generateDeployment("nginx")
   318  	deployment.Spec.RolloutAfter = &rolloutAfter
   319  
   320  	matchingMS := generateMS(deployment)
   321  
   322  	matchingMSHigherReplicas := generateMS(deployment)
   323  	matchingMSHigherReplicas.Spec.Replicas = ptr.To[int32](2)
   324  
   325  	matchingMSDiffersInPlaceMutableFields := generateMS(deployment)
   326  	matchingMSDiffersInPlaceMutableFields.Spec.Template.Spec.NodeDrainTimeout = &metav1.Duration{Duration: 20 * time.Second}
   327  
   328  	oldMS := generateMS(deployment)
   329  	oldMS.Spec.Template.Spec.InfrastructureRef.Name = "changed-infra-ref"
   330  
   331  	msCreatedTwoBeforeRolloutAfter := generateMS(deployment)
   332  	msCreatedTwoBeforeRolloutAfter.CreationTimestamp = twoBeforeRolloutAfter
   333  
   334  	msCreatedAfterRolloutAfter := generateMS(deployment)
   335  	msCreatedAfterRolloutAfter.CreationTimestamp = oneAfterRolloutAfter
   336  
   337  	tests := []struct {
   338  		Name               string
   339  		deployment         clusterv1.MachineDeployment
   340  		msList             []*clusterv1.MachineSet
   341  		reconciliationTime *metav1.Time
   342  		expected           *clusterv1.MachineSet
   343  	}{
   344  		{
   345  			Name:       "Get the MachineSet with the MachineTemplate that matches the intent of the MachineDeployment",
   346  			deployment: deployment,
   347  			msList:     []*clusterv1.MachineSet{&oldMS, &matchingMS},
   348  			expected:   &matchingMS,
   349  		},
   350  		{
   351  			Name:       "Get the MachineSet with the higher replicas if multiple MachineSets match the desired intent on the MachineDeployment",
   352  			deployment: deployment,
   353  			msList:     []*clusterv1.MachineSet{&oldMS, &matchingMS, &matchingMSHigherReplicas},
   354  			expected:   &matchingMSHigherReplicas,
   355  		},
   356  		{
   357  			Name:       "Get the MachineSet with the MachineTemplate that matches the desired intent on the MachineDeployment, except differs in in-place mutable fields",
   358  			deployment: deployment,
   359  			msList:     []*clusterv1.MachineSet{&oldMS, &matchingMSDiffersInPlaceMutableFields},
   360  			expected:   &matchingMSDiffersInPlaceMutableFields,
   361  		},
   362  		{
   363  			Name:       "Get nil if no MachineSet matches the desired intent of the MachineDeployment",
   364  			deployment: deployment,
   365  			msList:     []*clusterv1.MachineSet{&oldMS},
   366  			expected:   nil,
   367  		},
   368  		{
   369  			Name:               "Get the MachineSet if reconciliationTime < rolloutAfter",
   370  			deployment:         deployment,
   371  			msList:             []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter},
   372  			reconciliationTime: &oneBeforeRolloutAfter,
   373  			expected:           &msCreatedTwoBeforeRolloutAfter,
   374  		},
   375  		{
   376  			Name:               "Get nil if reconciliationTime is > rolloutAfter and no MachineSet is created after rolloutAfter",
   377  			deployment:         deployment,
   378  			msList:             []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter},
   379  			reconciliationTime: &oneAfterRolloutAfter,
   380  			expected:           nil,
   381  		},
   382  		{
   383  			Name:               "Get MachineSet created after RolloutAfter if reconciliationTime is > rolloutAfter",
   384  			deployment:         deployment,
   385  			msList:             []*clusterv1.MachineSet{&msCreatedAfterRolloutAfter, &msCreatedTwoBeforeRolloutAfter},
   386  			reconciliationTime: &twoAfterRolloutAfter,
   387  			expected:           &msCreatedAfterRolloutAfter,
   388  		},
   389  	}
   390  
   391  	for i := range tests {
   392  		test := tests[i]
   393  		t.Run(test.Name, func(t *testing.T) {
   394  			g := NewWithT(t)
   395  
   396  			ms := FindNewMachineSet(&test.deployment, test.msList, test.reconciliationTime)
   397  			g.Expect(ms).To(BeComparableTo(test.expected))
   398  		})
   399  	}
   400  }
   401  
   402  func TestFindOldMachineSets(t *testing.T) {
   403  	twoBeforeRolloutAfter := metav1.Now()
   404  	oneBeforeRolloutAfter := metav1.NewTime(twoBeforeRolloutAfter.Add(time.Minute))
   405  	rolloutAfter := metav1.NewTime(oneBeforeRolloutAfter.Add(time.Minute))
   406  	oneAfterRolloutAfter := metav1.NewTime(rolloutAfter.Add(time.Minute))
   407  	twoAfterRolloutAfter := metav1.NewTime(oneAfterRolloutAfter.Add(time.Minute))
   408  
   409  	deployment := generateDeployment("nginx")
   410  	deployment.Spec.RolloutAfter = &rolloutAfter
   411  
   412  	newMS := generateMS(deployment)
   413  	newMS.Name = "aa"
   414  	newMS.Spec.Replicas = ptr.To[int32](1)
   415  
   416  	newMSHigherReplicas := generateMS(deployment)
   417  	newMSHigherReplicas.Spec.Replicas = ptr.To[int32](2)
   418  
   419  	newMSHigherName := generateMS(deployment)
   420  	newMSHigherName.Spec.Replicas = ptr.To[int32](1)
   421  	newMSHigherName.Name = "ab"
   422  
   423  	oldDeployment := generateDeployment("nginx")
   424  	oldDeployment.Spec.Template.Spec.InfrastructureRef.Name = "changed-infra-ref"
   425  	oldMS := generateMS(oldDeployment)
   426  
   427  	msCreatedTwoBeforeRolloutAfter := generateMS(deployment)
   428  	msCreatedTwoBeforeRolloutAfter.CreationTimestamp = twoBeforeRolloutAfter
   429  
   430  	msCreatedAfterRolloutAfter := generateMS(deployment)
   431  	msCreatedAfterRolloutAfter.CreationTimestamp = oneAfterRolloutAfter
   432  
   433  	tests := []struct {
   434  		Name               string
   435  		deployment         clusterv1.MachineDeployment
   436  		msList             []*clusterv1.MachineSet
   437  		reconciliationTime *metav1.Time
   438  		expected           []*clusterv1.MachineSet
   439  	}{
   440  		{
   441  			Name:       "Get old MachineSets",
   442  			deployment: deployment,
   443  			msList:     []*clusterv1.MachineSet{&newMS, &oldMS},
   444  			expected:   []*clusterv1.MachineSet{&oldMS},
   445  		},
   446  		{
   447  			Name:       "Get old MachineSets with no new MachineSet",
   448  			deployment: deployment,
   449  			msList:     []*clusterv1.MachineSet{&oldMS},
   450  			expected:   []*clusterv1.MachineSet{&oldMS},
   451  		},
   452  		{
   453  			Name:       "Get old MachineSets with two new MachineSets, only the MachineSet with higher replicas is seen as new MachineSet",
   454  			deployment: deployment,
   455  			msList:     []*clusterv1.MachineSet{&oldMS, &newMS, &newMSHigherReplicas},
   456  			expected:   []*clusterv1.MachineSet{&oldMS, &newMS},
   457  		},
   458  		{
   459  			Name:       "Get old MachineSets with two new MachineSets, when replicas are matching only the MachineSet with lower name is seen as new MachineSet",
   460  			deployment: deployment,
   461  			msList:     []*clusterv1.MachineSet{&oldMS, &newMS, &newMSHigherName},
   462  			expected:   []*clusterv1.MachineSet{&oldMS, &newMSHigherName},
   463  		},
   464  		{
   465  			Name:       "Get empty old MachineSets",
   466  			deployment: deployment,
   467  			msList:     []*clusterv1.MachineSet{&newMS},
   468  			expected:   []*clusterv1.MachineSet{},
   469  		},
   470  		{
   471  			Name:               "Get old MachineSets with new MachineSets, MachineSets created before rolloutAfter are considered new when reconciliationTime < rolloutAfter",
   472  			deployment:         deployment,
   473  			msList:             []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter},
   474  			reconciliationTime: &oneBeforeRolloutAfter,
   475  			expected:           nil,
   476  		},
   477  		{
   478  			Name:               "Get old MachineSets with new MachineSets, MachineSets created after rolloutAfter are considered new when reconciliationTime > rolloutAfter",
   479  			deployment:         deployment,
   480  			msList:             []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter, &msCreatedAfterRolloutAfter},
   481  			reconciliationTime: &twoAfterRolloutAfter,
   482  			expected:           []*clusterv1.MachineSet{&msCreatedTwoBeforeRolloutAfter},
   483  		},
   484  	}
   485  
   486  	for i := range tests {
   487  		test := tests[i]
   488  		t.Run(test.Name, func(t *testing.T) {
   489  			g := NewWithT(t)
   490  
   491  			allMS := FindOldMachineSets(&test.deployment, test.msList, test.reconciliationTime)
   492  			g.Expect(allMS).To(ConsistOf(test.expected))
   493  		})
   494  	}
   495  }
   496  
   497  func TestGetReplicaCountForMachineSets(t *testing.T) {
   498  	ms1 := generateMS(generateDeployment("foo"))
   499  	*(ms1.Spec.Replicas) = 1
   500  	ms1.Status.Replicas = 2
   501  	ms2 := generateMS(generateDeployment("bar"))
   502  	*(ms2.Spec.Replicas) = 5
   503  	ms2.Status.Replicas = 3
   504  
   505  	tests := []struct {
   506  		Name           string
   507  		Sets           []*clusterv1.MachineSet
   508  		ExpectedCount  int32
   509  		ExpectedActual int32
   510  		ExpectedTotal  int32
   511  	}{
   512  		{
   513  			Name:           "1:2 Replicas",
   514  			Sets:           []*clusterv1.MachineSet{&ms1},
   515  			ExpectedCount:  1,
   516  			ExpectedActual: 2,
   517  			ExpectedTotal:  2,
   518  		},
   519  		{
   520  			Name:           "6:5 Replicas",
   521  			Sets:           []*clusterv1.MachineSet{&ms1, &ms2},
   522  			ExpectedCount:  6,
   523  			ExpectedActual: 5,
   524  			ExpectedTotal:  7,
   525  		},
   526  	}
   527  
   528  	for _, test := range tests {
   529  		t.Run(test.Name, func(t *testing.T) {
   530  			g := NewWithT(t)
   531  
   532  			g.Expect(GetReplicaCountForMachineSets(test.Sets)).To(Equal(test.ExpectedCount))
   533  			g.Expect(GetActualReplicaCountForMachineSets(test.Sets)).To(Equal(test.ExpectedActual))
   534  			g.Expect(TotalMachineSetsReplicaSum(test.Sets)).To(Equal(test.ExpectedTotal))
   535  		})
   536  	}
   537  }
   538  
   539  func TestResolveFenceposts(t *testing.T) {
   540  	tests := []struct {
   541  		maxSurge          string
   542  		maxUnavailable    string
   543  		desired           int32
   544  		expectSurge       int32
   545  		expectUnavailable int32
   546  		expectError       bool
   547  	}{
   548  		{
   549  			maxSurge:          "0%",
   550  			maxUnavailable:    "0%",
   551  			desired:           0,
   552  			expectSurge:       0,
   553  			expectUnavailable: 1,
   554  			expectError:       false,
   555  		},
   556  		{
   557  			maxSurge:          "39%",
   558  			maxUnavailable:    "39%",
   559  			desired:           10,
   560  			expectSurge:       4,
   561  			expectUnavailable: 3,
   562  			expectError:       false,
   563  		},
   564  		{
   565  			maxSurge:          "oops",
   566  			maxUnavailable:    "39%",
   567  			desired:           10,
   568  			expectSurge:       0,
   569  			expectUnavailable: 0,
   570  			expectError:       true,
   571  		},
   572  		{
   573  			maxSurge:          "55%",
   574  			maxUnavailable:    "urg",
   575  			desired:           10,
   576  			expectSurge:       0,
   577  			expectUnavailable: 0,
   578  			expectError:       true,
   579  		},
   580  		{
   581  			maxSurge:          "5",
   582  			maxUnavailable:    "1",
   583  			desired:           7,
   584  			expectSurge:       0,
   585  			expectUnavailable: 0,
   586  			expectError:       true,
   587  		},
   588  	}
   589  
   590  	for _, test := range tests {
   591  		t.Run("maxSurge="+test.maxSurge, func(t *testing.T) {
   592  			g := NewWithT(t)
   593  
   594  			maxSurge := intstr.FromString(test.maxSurge)
   595  			maxUnavail := intstr.FromString(test.maxUnavailable)
   596  			surge, unavail, err := ResolveFenceposts(&maxSurge, &maxUnavail, test.desired)
   597  			if test.expectError {
   598  				g.Expect(err).To(HaveOccurred())
   599  			} else {
   600  				g.Expect(err).ToNot(HaveOccurred())
   601  			}
   602  			g.Expect(surge).To(Equal(test.expectSurge))
   603  			g.Expect(unavail).To(Equal(test.expectUnavailable))
   604  		})
   605  	}
   606  }
   607  
   608  func TestNewMSNewReplicas(t *testing.T) {
   609  	tests := []struct {
   610  		Name          string
   611  		strategyType  clusterv1.MachineDeploymentStrategyType
   612  		depReplicas   int32
   613  		newMSReplicas int32
   614  		maxSurge      int
   615  		expected      int32
   616  	}{
   617  		{
   618  			"can not scale up - to newMSReplicas",
   619  			clusterv1.RollingUpdateMachineDeploymentStrategyType,
   620  			1, 5, 1, 5,
   621  		},
   622  		{
   623  			"scale up - to depReplicas",
   624  			clusterv1.RollingUpdateMachineDeploymentStrategyType,
   625  			6, 2, 10, 6,
   626  		},
   627  	}
   628  	newDeployment := generateDeployment("nginx")
   629  	newRC := generateMS(newDeployment)
   630  	rs5 := generateMS(newDeployment)
   631  	*(rs5.Spec.Replicas) = 5
   632  
   633  	for _, test := range tests {
   634  		t.Run(test.Name, func(t *testing.T) {
   635  			g := NewWithT(t)
   636  
   637  			*(newDeployment.Spec.Replicas) = test.depReplicas
   638  			newDeployment.Spec.Strategy = &clusterv1.MachineDeploymentStrategy{Type: test.strategyType}
   639  			newDeployment.Spec.Strategy.RollingUpdate = &clusterv1.MachineRollingUpdateDeployment{
   640  				MaxUnavailable: func(i int) *intstr.IntOrString {
   641  					x := intstr.FromInt(i)
   642  					return &x
   643  				}(1),
   644  				MaxSurge: func(i int) *intstr.IntOrString {
   645  					x := intstr.FromInt(i)
   646  					return &x
   647  				}(test.maxSurge),
   648  			}
   649  			*(newRC.Spec.Replicas) = test.newMSReplicas
   650  			ms, err := NewMSNewReplicas(&newDeployment, []*clusterv1.MachineSet{&rs5}, *newRC.Spec.Replicas)
   651  			g.Expect(err).ToNot(HaveOccurred())
   652  			g.Expect(ms).To(Equal(test.expected))
   653  		})
   654  	}
   655  }
   656  
   657  func TestDeploymentComplete(t *testing.T) {
   658  	deployment := func(desired, current, updated, available, maxUnavailable, maxSurge int32) *clusterv1.MachineDeployment {
   659  		return &clusterv1.MachineDeployment{
   660  			Spec: clusterv1.MachineDeploymentSpec{
   661  				Replicas: &desired,
   662  				Strategy: &clusterv1.MachineDeploymentStrategy{
   663  					RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   664  						MaxUnavailable: func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(maxUnavailable)),
   665  						MaxSurge:       func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(maxSurge)),
   666  					},
   667  					Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   668  				},
   669  			},
   670  			Status: clusterv1.MachineDeploymentStatus{
   671  				Replicas:          current,
   672  				UpdatedReplicas:   updated,
   673  				AvailableReplicas: available,
   674  			},
   675  		}
   676  	}
   677  
   678  	tests := []struct {
   679  		name string
   680  
   681  		md *clusterv1.MachineDeployment
   682  
   683  		expected bool
   684  	}{
   685  		{
   686  			name: "not complete: min but not all machines become available",
   687  
   688  			md:       deployment(5, 5, 5, 4, 1, 0),
   689  			expected: false,
   690  		},
   691  		{
   692  			name: "not complete: min availability is not honored",
   693  
   694  			md:       deployment(5, 5, 5, 3, 1, 0),
   695  			expected: false,
   696  		},
   697  		{
   698  			name: "complete",
   699  
   700  			md:       deployment(5, 5, 5, 5, 0, 0),
   701  			expected: true,
   702  		},
   703  		{
   704  			name: "not complete: all machines are available but not updated",
   705  
   706  			md:       deployment(5, 5, 4, 5, 0, 0),
   707  			expected: false,
   708  		},
   709  		{
   710  			name: "not complete: still running old machines",
   711  
   712  			// old machine set: spec.replicas=1, status.replicas=1, status.availableReplicas=1
   713  			// new machine set: spec.replicas=1, status.replicas=1, status.availableReplicas=0
   714  			md:       deployment(1, 2, 1, 1, 0, 1),
   715  			expected: false,
   716  		},
   717  		{
   718  			name: "not complete: one replica deployment never comes up",
   719  
   720  			md:       deployment(1, 1, 1, 0, 1, 1),
   721  			expected: false,
   722  		},
   723  	}
   724  
   725  	for i := range tests {
   726  		test := tests[i]
   727  		t.Run(test.name, func(t *testing.T) {
   728  			g := NewWithT(t)
   729  
   730  			g.Expect(DeploymentComplete(test.md, &test.md.Status)).To(Equal(test.expected))
   731  		})
   732  	}
   733  }
   734  
   735  func TestMaxUnavailable(t *testing.T) {
   736  	deployment := func(replicas int32, maxUnavailable intstr.IntOrString) clusterv1.MachineDeployment {
   737  		return clusterv1.MachineDeployment{
   738  			Spec: clusterv1.MachineDeploymentSpec{
   739  				Replicas: func(i int32) *int32 { return &i }(replicas),
   740  				Strategy: &clusterv1.MachineDeploymentStrategy{
   741  					RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   742  						MaxSurge:       func(i int) *intstr.IntOrString { x := intstr.FromInt(i); return &x }(int(1)),
   743  						MaxUnavailable: &maxUnavailable,
   744  					},
   745  					Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   746  				},
   747  			},
   748  		}
   749  	}
   750  	tests := []struct {
   751  		name       string
   752  		deployment clusterv1.MachineDeployment
   753  		expected   int32
   754  	}{
   755  		{
   756  			name:       "maxUnavailable less than replicas",
   757  			deployment: deployment(10, intstr.FromInt(5)),
   758  			expected:   int32(5),
   759  		},
   760  		{
   761  			name:       "maxUnavailable equal replicas",
   762  			deployment: deployment(10, intstr.FromInt(10)),
   763  			expected:   int32(10),
   764  		},
   765  		{
   766  			name:       "maxUnavailable greater than replicas",
   767  			deployment: deployment(5, intstr.FromInt(10)),
   768  			expected:   int32(5),
   769  		},
   770  		{
   771  			name:       "maxUnavailable with replicas is 0",
   772  			deployment: deployment(0, intstr.FromInt(10)),
   773  			expected:   int32(0),
   774  		},
   775  		{
   776  			name:       "maxUnavailable less than replicas with percents",
   777  			deployment: deployment(10, intstr.FromString("50%")),
   778  			expected:   int32(5),
   779  		},
   780  		{
   781  			name:       "maxUnavailable equal replicas with percents",
   782  			deployment: deployment(10, intstr.FromString("100%")),
   783  			expected:   int32(10),
   784  		},
   785  		{
   786  			name:       "maxUnavailable greater than replicas with percents",
   787  			deployment: deployment(5, intstr.FromString("100%")),
   788  			expected:   int32(5),
   789  		},
   790  	}
   791  
   792  	for _, test := range tests {
   793  		t.Run(test.name, func(t *testing.T) {
   794  			g := NewWithT(t)
   795  
   796  			g.Expect(MaxUnavailable(test.deployment)).To(Equal(test.expected))
   797  		})
   798  	}
   799  }
   800  
   801  // TestAnnotationUtils is a set of simple tests for annotation related util functions.
   802  func TestAnnotationUtils(t *testing.T) {
   803  	// Setup
   804  	tDeployment := generateDeployment("nginx")
   805  	tDeployment.Spec.Replicas = ptr.To[int32](1)
   806  	tMS := generateMS(tDeployment)
   807  
   808  	// Test Case 1:  Check if annotations are set properly
   809  	t.Run("SetReplicasAnnotations", func(t *testing.T) {
   810  		g := NewWithT(t)
   811  
   812  		g.Expect(SetReplicasAnnotations(&tMS, 10, 11)).To(BeTrue())
   813  		g.Expect(tMS.Annotations).To(HaveKeyWithValue(clusterv1.DesiredReplicasAnnotation, "10"))
   814  		g.Expect(tMS.Annotations).To(HaveKeyWithValue(clusterv1.MaxReplicasAnnotation, "11"))
   815  	})
   816  
   817  	// Test Case 2:  Check if annotations reflect deployments state
   818  	tMS.Annotations[clusterv1.DesiredReplicasAnnotation] = "1"
   819  	tMS.Status.AvailableReplicas = 1
   820  	tMS.Spec.Replicas = new(int32)
   821  	*tMS.Spec.Replicas = 1
   822  
   823  	t.Run("IsSaturated", func(t *testing.T) {
   824  		g := NewWithT(t)
   825  
   826  		g.Expect(IsSaturated(&tDeployment, &tMS)).To(BeTrue())
   827  	})
   828  }
   829  
   830  func TestComputeMachineSetAnnotations(t *testing.T) {
   831  	deployment := generateDeployment("nginx")
   832  	deployment.Spec.Replicas = ptr.To[int32](3)
   833  	maxSurge := intstr.FromInt(1)
   834  	maxUnavailable := intstr.FromInt(0)
   835  	deployment.Spec.Strategy = &clusterv1.MachineDeploymentStrategy{
   836  		Type: clusterv1.RollingUpdateMachineDeploymentStrategyType,
   837  		RollingUpdate: &clusterv1.MachineRollingUpdateDeployment{
   838  			MaxSurge:       &maxSurge,
   839  			MaxUnavailable: &maxUnavailable,
   840  		},
   841  	}
   842  	deployment.Annotations = map[string]string{
   843  		corev1.LastAppliedConfigAnnotation: "last-applied-configuration",
   844  		"key1":                             "value1",
   845  	}
   846  
   847  	tests := []struct {
   848  		name       string
   849  		deployment *clusterv1.MachineDeployment
   850  		oldMSs     []*clusterv1.MachineSet
   851  		ms         *clusterv1.MachineSet
   852  		want       map[string]string
   853  		wantErr    bool
   854  	}{
   855  		{
   856  			name:       "Calculating annotations for a new MachineSet",
   857  			deployment: &deployment,
   858  			oldMSs:     nil,
   859  			ms:         nil,
   860  			want: map[string]string{
   861  				"key1":                              "value1",
   862  				clusterv1.RevisionAnnotation:        "1",
   863  				clusterv1.DesiredReplicasAnnotation: "3",
   864  				clusterv1.MaxReplicasAnnotation:     "4",
   865  			},
   866  			wantErr: false,
   867  		},
   868  		{
   869  			name:       "Calculating annotations for a new MachineSet - old MSs exist",
   870  			deployment: &deployment,
   871  			oldMSs:     []*clusterv1.MachineSet{machineSetWithRevisionAndHistory("1", "")},
   872  			ms:         nil,
   873  			want: map[string]string{
   874  				"key1":                              "value1",
   875  				clusterv1.RevisionAnnotation:        "2",
   876  				clusterv1.DesiredReplicasAnnotation: "3",
   877  				clusterv1.MaxReplicasAnnotation:     "4",
   878  			},
   879  			wantErr: false,
   880  		},
   881  		{
   882  			name:       "Calculating annotations for a existing MachineSet",
   883  			deployment: &deployment,
   884  			oldMSs:     nil,
   885  			ms:         machineSetWithRevisionAndHistory("1", ""),
   886  			want: map[string]string{
   887  				"key1":                              "value1",
   888  				clusterv1.RevisionAnnotation:        "1",
   889  				clusterv1.DesiredReplicasAnnotation: "3",
   890  				clusterv1.MaxReplicasAnnotation:     "4",
   891  			},
   892  			wantErr: false,
   893  		},
   894  		{
   895  			name:       "Calculating annotations for a existing MachineSet - old MSs exist",
   896  			deployment: &deployment,
   897  			oldMSs: []*clusterv1.MachineSet{
   898  				machineSetWithRevisionAndHistory("1", ""),
   899  				machineSetWithRevisionAndHistory("2", ""),
   900  			},
   901  			ms: machineSetWithRevisionAndHistory("1", ""),
   902  			want: map[string]string{
   903  				"key1":                              "value1",
   904  				clusterv1.RevisionAnnotation:        "3",
   905  				clusterv1.RevisionHistoryAnnotation: "1",
   906  				clusterv1.DesiredReplicasAnnotation: "3",
   907  				clusterv1.MaxReplicasAnnotation:     "4",
   908  			},
   909  			wantErr: false,
   910  		},
   911  		{
   912  			name:       "Calculating annotations for a existing MachineSet - old MSs exist - existing revision is greater",
   913  			deployment: &deployment,
   914  			oldMSs: []*clusterv1.MachineSet{
   915  				machineSetWithRevisionAndHistory("1", ""),
   916  				machineSetWithRevisionAndHistory("2", ""),
   917  			},
   918  			ms: machineSetWithRevisionAndHistory("4", ""),
   919  			want: map[string]string{
   920  				"key1":                              "value1",
   921  				clusterv1.RevisionAnnotation:        "4",
   922  				clusterv1.DesiredReplicasAnnotation: "3",
   923  				clusterv1.MaxReplicasAnnotation:     "4",
   924  			},
   925  			wantErr: false,
   926  		},
   927  		{
   928  			name:       "Calculating annotations for a existing MachineSet - old MSs exist - ms already has revision history",
   929  			deployment: &deployment,
   930  			oldMSs: []*clusterv1.MachineSet{
   931  				machineSetWithRevisionAndHistory("3", ""),
   932  				machineSetWithRevisionAndHistory("4", ""),
   933  			},
   934  			ms: machineSetWithRevisionAndHistory("2", "1"),
   935  			want: map[string]string{
   936  				"key1":                              "value1",
   937  				clusterv1.RevisionAnnotation:        "5",
   938  				clusterv1.RevisionHistoryAnnotation: "1,2",
   939  				clusterv1.DesiredReplicasAnnotation: "3",
   940  				clusterv1.MaxReplicasAnnotation:     "4",
   941  			},
   942  			wantErr: false,
   943  		},
   944  	}
   945  
   946  	for _, tt := range tests {
   947  		t.Run(tt.name, func(t *testing.T) {
   948  			g := NewWithT(t)
   949  			got, err := ComputeMachineSetAnnotations(ctx, tt.deployment, tt.oldMSs, tt.ms)
   950  			if tt.wantErr {
   951  				g.Expect(err).ShouldNot(HaveOccurred())
   952  			} else {
   953  				g.Expect(err).ToNot(HaveOccurred())
   954  				g.Expect(got).Should(Equal(tt.want))
   955  			}
   956  		})
   957  	}
   958  }
   959  
   960  func machineSetWithRevisionAndHistory(revision string, revisionHistory string) *clusterv1.MachineSet {
   961  	ms := &clusterv1.MachineSet{
   962  		ObjectMeta: metav1.ObjectMeta{
   963  			Annotations: map[string]string{
   964  				clusterv1.RevisionAnnotation: revision,
   965  			},
   966  		},
   967  	}
   968  	if revisionHistory != "" {
   969  		ms.Annotations[clusterv1.RevisionHistoryAnnotation] = revisionHistory
   970  	}
   971  	return ms
   972  }
   973  
   974  func TestReplicasAnnotationsNeedUpdate(t *testing.T) {
   975  	desiredReplicas := fmt.Sprintf("%d", int32(10))
   976  	maxReplicas := fmt.Sprintf("%d", int32(20))
   977  
   978  	tests := []struct {
   979  		name       string
   980  		machineSet *clusterv1.MachineSet
   981  		expected   bool
   982  	}{
   983  		{
   984  			name: "test Annotations nil",
   985  			machineSet: &clusterv1.MachineSet{
   986  				ObjectMeta: metav1.ObjectMeta{Name: "hello", Namespace: metav1.NamespaceDefault},
   987  				Spec: clusterv1.MachineSetSpec{
   988  					Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
   989  				},
   990  			},
   991  			expected: true,
   992  		},
   993  		{
   994  			name: "test desiredReplicas update",
   995  			machineSet: &clusterv1.MachineSet{
   996  				ObjectMeta: metav1.ObjectMeta{
   997  					Name:        "hello",
   998  					Namespace:   metav1.NamespaceDefault,
   999  					Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: "8", clusterv1.MaxReplicasAnnotation: maxReplicas},
  1000  				},
  1001  				Spec: clusterv1.MachineSetSpec{
  1002  					Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  1003  				},
  1004  			},
  1005  			expected: true,
  1006  		},
  1007  		{
  1008  			name: "test maxReplicas update",
  1009  			machineSet: &clusterv1.MachineSet{
  1010  				ObjectMeta: metav1.ObjectMeta{
  1011  					Name:        "hello",
  1012  					Namespace:   metav1.NamespaceDefault,
  1013  					Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: desiredReplicas, clusterv1.MaxReplicasAnnotation: "16"},
  1014  				},
  1015  				Spec: clusterv1.MachineSetSpec{
  1016  					Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  1017  				},
  1018  			},
  1019  			expected: true,
  1020  		},
  1021  		{
  1022  			name: "test needn't update",
  1023  			machineSet: &clusterv1.MachineSet{
  1024  				ObjectMeta: metav1.ObjectMeta{
  1025  					Name:        "hello",
  1026  					Namespace:   metav1.NamespaceDefault,
  1027  					Annotations: map[string]string{clusterv1.DesiredReplicasAnnotation: desiredReplicas, clusterv1.MaxReplicasAnnotation: maxReplicas},
  1028  				},
  1029  				Spec: clusterv1.MachineSetSpec{
  1030  					Selector: metav1.LabelSelector{MatchLabels: map[string]string{"foo": "bar"}},
  1031  				},
  1032  			},
  1033  			expected: false,
  1034  		},
  1035  	}
  1036  
  1037  	for _, test := range tests {
  1038  		t.Run(test.name, func(t *testing.T) {
  1039  			g := NewWithT(t)
  1040  
  1041  			g.Expect(ReplicasAnnotationsNeedUpdate(test.machineSet, 10, 20)).To(Equal(test.expected))
  1042  		})
  1043  	}
  1044  }