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