sigs.k8s.io/cluster-api@v1.7.1/internal/contract/controlplane_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 contract
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    26  
    27  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    28  )
    29  
    30  func TestControlPlane(t *testing.T) {
    31  	obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
    32  
    33  	t.Run("Manages spec.version", func(t *testing.T) {
    34  		g := NewWithT(t)
    35  
    36  		g.Expect(ControlPlane().Version().Path()).To(Equal(Path{"spec", "version"}))
    37  
    38  		err := ControlPlane().Version().Set(obj, "vFoo")
    39  		g.Expect(err).ToNot(HaveOccurred())
    40  
    41  		got, err := ControlPlane().Version().Get(obj)
    42  		g.Expect(err).ToNot(HaveOccurred())
    43  		g.Expect(got).ToNot(BeNil())
    44  		g.Expect(*got).To(Equal("vFoo"))
    45  	})
    46  	t.Run("Manages status.version", func(t *testing.T) {
    47  		g := NewWithT(t)
    48  
    49  		g.Expect(ControlPlane().StatusVersion().Path()).To(Equal(Path{"status", "version"}))
    50  
    51  		err := ControlPlane().StatusVersion().Set(obj, "1.2.3")
    52  		g.Expect(err).ToNot(HaveOccurred())
    53  
    54  		got, err := ControlPlane().StatusVersion().Get(obj)
    55  		g.Expect(err).ToNot(HaveOccurred())
    56  		g.Expect(got).ToNot(BeNil())
    57  		g.Expect(*got).To(Equal("1.2.3"))
    58  	})
    59  	t.Run("Manages status.ready", func(t *testing.T) {
    60  		g := NewWithT(t)
    61  
    62  		g.Expect(ControlPlane().Ready().Path()).To(Equal(Path{"status", "ready"}))
    63  
    64  		err := ControlPlane().Ready().Set(obj, true)
    65  		g.Expect(err).ToNot(HaveOccurred())
    66  
    67  		got, err := ControlPlane().Ready().Get(obj)
    68  		g.Expect(err).ToNot(HaveOccurred())
    69  		g.Expect(got).ToNot(BeNil())
    70  		g.Expect(*got).To(BeTrue())
    71  	})
    72  	t.Run("Manages status.initialized", func(t *testing.T) {
    73  		g := NewWithT(t)
    74  
    75  		g.Expect(ControlPlane().Initialized().Path()).To(Equal(Path{"status", "initialized"}))
    76  
    77  		err := ControlPlane().Initialized().Set(obj, true)
    78  		g.Expect(err).ToNot(HaveOccurred())
    79  
    80  		got, err := ControlPlane().Initialized().Get(obj)
    81  		g.Expect(err).ToNot(HaveOccurred())
    82  		g.Expect(got).ToNot(BeNil())
    83  		g.Expect(*got).To(BeTrue())
    84  	})
    85  	t.Run("Manages spec.replicas", func(t *testing.T) {
    86  		g := NewWithT(t)
    87  
    88  		g.Expect(ControlPlane().Replicas().Path()).To(Equal(Path{"spec", "replicas"}))
    89  
    90  		err := ControlPlane().Replicas().Set(obj, int64(3))
    91  		g.Expect(err).ToNot(HaveOccurred())
    92  
    93  		got, err := ControlPlane().Replicas().Get(obj)
    94  		g.Expect(err).ToNot(HaveOccurred())
    95  		g.Expect(got).ToNot(BeNil())
    96  		g.Expect(*got).To(Equal(int64(3)))
    97  	})
    98  	t.Run("Manages status.replicas", func(t *testing.T) {
    99  		g := NewWithT(t)
   100  
   101  		g.Expect(ControlPlane().StatusReplicas().Path()).To(Equal(Path{"status", "replicas"}))
   102  
   103  		err := ControlPlane().StatusReplicas().Set(obj, int64(3))
   104  		g.Expect(err).ToNot(HaveOccurred())
   105  
   106  		got, err := ControlPlane().StatusReplicas().Get(obj)
   107  		g.Expect(err).ToNot(HaveOccurred())
   108  		g.Expect(got).ToNot(BeNil())
   109  		g.Expect(*got).To(Equal(int64(3)))
   110  	})
   111  	t.Run("Manages status.updatedreplicas", func(t *testing.T) {
   112  		g := NewWithT(t)
   113  
   114  		g.Expect(ControlPlane().UpdatedReplicas().Path()).To(Equal(Path{"status", "updatedReplicas"}))
   115  
   116  		err := ControlPlane().UpdatedReplicas().Set(obj, int64(3))
   117  		g.Expect(err).ToNot(HaveOccurred())
   118  
   119  		got, err := ControlPlane().UpdatedReplicas().Get(obj)
   120  		g.Expect(err).ToNot(HaveOccurred())
   121  		g.Expect(got).ToNot(BeNil())
   122  		g.Expect(*got).To(Equal(int64(3)))
   123  	})
   124  	t.Run("Manages status.readyReplicas", func(t *testing.T) {
   125  		g := NewWithT(t)
   126  
   127  		g.Expect(ControlPlane().ReadyReplicas().Path()).To(Equal(Path{"status", "readyReplicas"}))
   128  
   129  		err := ControlPlane().ReadyReplicas().Set(obj, int64(3))
   130  		g.Expect(err).ToNot(HaveOccurred())
   131  
   132  		got, err := ControlPlane().ReadyReplicas().Get(obj)
   133  		g.Expect(err).ToNot(HaveOccurred())
   134  		g.Expect(got).ToNot(BeNil())
   135  		g.Expect(*got).To(Equal(int64(3)))
   136  	})
   137  	t.Run("Manages status.unavailableReplicas", func(t *testing.T) {
   138  		g := NewWithT(t)
   139  
   140  		g.Expect(ControlPlane().UnavailableReplicas().Path()).To(Equal(Path{"status", "unavailableReplicas"}))
   141  
   142  		err := ControlPlane().UnavailableReplicas().Set(obj, int64(3))
   143  		g.Expect(err).ToNot(HaveOccurred())
   144  
   145  		got, err := ControlPlane().UnavailableReplicas().Get(obj)
   146  		g.Expect(err).ToNot(HaveOccurred())
   147  		g.Expect(got).ToNot(BeNil())
   148  		g.Expect(*got).To(Equal(int64(3)))
   149  	})
   150  	t.Run("Manages status.selector", func(t *testing.T) {
   151  		g := NewWithT(t)
   152  
   153  		g.Expect(ControlPlane().Selector().Path()).To(Equal(Path{"status", "selector"}))
   154  
   155  		err := ControlPlane().Selector().Set(obj, "my-selector")
   156  		g.Expect(err).ToNot(HaveOccurred())
   157  
   158  		got, err := ControlPlane().Selector().Get(obj)
   159  		g.Expect(err).ToNot(HaveOccurred())
   160  		g.Expect(got).ToNot(BeNil())
   161  		g.Expect(*got).To(Equal("my-selector"))
   162  	})
   163  	t.Run("Manages spec.machineTemplate.infrastructureRef", func(t *testing.T) {
   164  		g := NewWithT(t)
   165  
   166  		refObj := fooRefBuilder()
   167  
   168  		g.Expect(ControlPlane().MachineTemplate().InfrastructureRef().Path()).To(Equal(Path{"spec", "machineTemplate", "infrastructureRef"}))
   169  
   170  		err := ControlPlane().MachineTemplate().InfrastructureRef().Set(obj, refObj)
   171  		g.Expect(err).ToNot(HaveOccurred())
   172  
   173  		got, err := ControlPlane().MachineTemplate().InfrastructureRef().Get(obj)
   174  		g.Expect(err).ToNot(HaveOccurred())
   175  		g.Expect(got).ToNot(BeNil())
   176  		g.Expect(got.APIVersion).To(Equal(refObj.GetAPIVersion()))
   177  		g.Expect(got.Kind).To(Equal(refObj.GetKind()))
   178  		g.Expect(got.Name).To(Equal(refObj.GetName()))
   179  		g.Expect(got.Namespace).To(Equal(refObj.GetNamespace()))
   180  	})
   181  	t.Run("Manages spec.machineTemplate.metadata", func(t *testing.T) {
   182  		g := NewWithT(t)
   183  
   184  		metadata := &clusterv1.ObjectMeta{
   185  			Labels: map[string]string{
   186  				"label1": "labelValue1",
   187  			},
   188  			Annotations: map[string]string{
   189  				"annotation1": "annotationValue1",
   190  			},
   191  		}
   192  
   193  		g.Expect(ControlPlane().MachineTemplate().Metadata().Path()).To(Equal(Path{"spec", "machineTemplate", "metadata"}))
   194  
   195  		err := ControlPlane().MachineTemplate().Metadata().Set(obj, metadata)
   196  		g.Expect(err).ToNot(HaveOccurred())
   197  
   198  		got, err := ControlPlane().MachineTemplate().Metadata().Get(obj)
   199  		g.Expect(err).ToNot(HaveOccurred())
   200  		g.Expect(got).ToNot(BeNil())
   201  		g.Expect(got).To(BeComparableTo(metadata))
   202  	})
   203  
   204  	t.Run("Manages spec.machineTemplate.nodeDrainTimeout", func(t *testing.T) {
   205  		g := NewWithT(t)
   206  
   207  		duration := metav1.Duration{Duration: 2*time.Minute + 5*time.Second}
   208  		expectedDurationString := "2m5s"
   209  		g.Expect(ControlPlane().MachineTemplate().NodeDrainTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeDrainTimeout"}))
   210  
   211  		err := ControlPlane().MachineTemplate().NodeDrainTimeout().Set(obj, duration)
   212  		g.Expect(err).ToNot(HaveOccurred())
   213  
   214  		got, err := ControlPlane().MachineTemplate().NodeDrainTimeout().Get(obj)
   215  		g.Expect(err).ToNot(HaveOccurred())
   216  		g.Expect(got).ToNot(BeNil())
   217  		g.Expect(*got).To(Equal(duration))
   218  
   219  		// Check that the literal string value of the duration is correctly formatted.
   220  		durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeDrainTimeout")
   221  		g.Expect(err).ToNot(HaveOccurred())
   222  		g.Expect(found).To(BeTrue())
   223  		g.Expect(durationString).To(Equal(expectedDurationString))
   224  	})
   225  
   226  	t.Run("Manages spec.machineTemplate.nodeVolumeDetachTimeout", func(t *testing.T) {
   227  		g := NewWithT(t)
   228  
   229  		duration := metav1.Duration{Duration: 2*time.Minute + 10*time.Second}
   230  		expectedDurationString := "2m10s"
   231  		g.Expect(ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeVolumeDetachTimeout"}))
   232  
   233  		err := ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Set(obj, duration)
   234  		g.Expect(err).ToNot(HaveOccurred())
   235  
   236  		got, err := ControlPlane().MachineTemplate().NodeVolumeDetachTimeout().Get(obj)
   237  		g.Expect(err).ToNot(HaveOccurred())
   238  		g.Expect(got).ToNot(BeNil())
   239  		g.Expect(*got).To(Equal(duration))
   240  
   241  		// Check that the literal string value of the duration is correctly formatted.
   242  		durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeVolumeDetachTimeout")
   243  		g.Expect(err).ToNot(HaveOccurred())
   244  		g.Expect(found).To(BeTrue())
   245  		g.Expect(durationString).To(Equal(expectedDurationString))
   246  	})
   247  
   248  	t.Run("Manages spec.machineTemplate.nodeDeletionTimeout", func(t *testing.T) {
   249  		g := NewWithT(t)
   250  
   251  		duration := metav1.Duration{Duration: 2*time.Minute + 5*time.Second}
   252  		expectedDurationString := "2m5s"
   253  		g.Expect(ControlPlane().MachineTemplate().NodeDeletionTimeout().Path()).To(Equal(Path{"spec", "machineTemplate", "nodeDeletionTimeout"}))
   254  
   255  		err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Set(obj, duration)
   256  		g.Expect(err).ToNot(HaveOccurred())
   257  
   258  		got, err := ControlPlane().MachineTemplate().NodeDeletionTimeout().Get(obj)
   259  		g.Expect(err).ToNot(HaveOccurred())
   260  		g.Expect(got).ToNot(BeNil())
   261  		g.Expect(*got).To(Equal(duration))
   262  
   263  		// Check that the literal string value of the duration is correctly formatted.
   264  		durationString, found, err := unstructured.NestedString(obj.UnstructuredContent(), "spec", "machineTemplate", "nodeDeletionTimeout")
   265  		g.Expect(err).ToNot(HaveOccurred())
   266  		g.Expect(found).To(BeTrue())
   267  		g.Expect(durationString).To(Equal(expectedDurationString))
   268  	})
   269  }
   270  
   271  func TestControlPlaneIsUpgrading(t *testing.T) {
   272  	tests := []struct {
   273  		name          string
   274  		obj           *unstructured.Unstructured
   275  		wantUpgrading bool
   276  	}{
   277  		{
   278  			name: "should return false if status is not set on control plane",
   279  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   280  				"spec": map[string]interface{}{
   281  					"version": "v1.2.3",
   282  				},
   283  			}},
   284  			wantUpgrading: false,
   285  		},
   286  		{
   287  			name: "should return false if status.version is not set on control plane",
   288  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   289  				"spec": map[string]interface{}{
   290  					"version": "v1.2.3",
   291  				},
   292  				"status": map[string]interface{}{},
   293  			}},
   294  			wantUpgrading: false,
   295  		},
   296  		{
   297  			name: "should return false if status.version is equal to spec.version",
   298  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   299  				"spec": map[string]interface{}{
   300  					"version": "v1.2.3",
   301  				},
   302  				"status": map[string]interface{}{
   303  					"version": "v1.2.3",
   304  				},
   305  			}},
   306  			wantUpgrading: false,
   307  		},
   308  		{
   309  			name: "should return true if status.version is less than spec.version",
   310  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   311  				"spec": map[string]interface{}{
   312  					"version": "v1.2.3",
   313  				},
   314  				"status": map[string]interface{}{
   315  					"version": "v1.2.2",
   316  				},
   317  			}},
   318  			wantUpgrading: true,
   319  		},
   320  		{
   321  			name: "should return false if status.version is greater than spec.version",
   322  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   323  				"spec": map[string]interface{}{
   324  					"version": "v1.2.2",
   325  				},
   326  				"status": map[string]interface{}{
   327  					"version": "v1.2.3",
   328  				},
   329  			}},
   330  			wantUpgrading: false,
   331  		},
   332  	}
   333  	for _, tt := range tests {
   334  		t.Run(tt.name, func(t *testing.T) {
   335  			g := NewWithT(t)
   336  
   337  			actual, _ := ControlPlane().IsUpgrading(tt.obj)
   338  			g.Expect(actual).To(Equal(tt.wantUpgrading))
   339  		})
   340  	}
   341  }
   342  
   343  func TestControlPlaneIsScaling(t *testing.T) {
   344  	tests := []struct {
   345  		name        string
   346  		obj         *unstructured.Unstructured
   347  		wantScaling bool
   348  	}{
   349  		{
   350  			name: "should return false for stable control plane",
   351  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   352  				"spec": map[string]interface{}{
   353  					"replicas": int64(2),
   354  				},
   355  				"status": map[string]interface{}{
   356  					"replicas":            int64(2),
   357  					"updatedReplicas":     int64(2),
   358  					"readyReplicas":       int64(2),
   359  					"unavailableReplicas": int64(0),
   360  				},
   361  			}},
   362  			wantScaling: false,
   363  		},
   364  		{
   365  			name: "should return true if status is not set on control plane",
   366  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   367  				"spec": map[string]interface{}{
   368  					"replicas": int64(2),
   369  				},
   370  			}},
   371  			wantScaling: true,
   372  		},
   373  		{
   374  			name: "should return true if status replicas is not set on control plane",
   375  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   376  				"spec": map[string]interface{}{
   377  					"replicas": int64(2),
   378  				},
   379  				"status": map[string]interface{}{
   380  					"updatedReplicas":     int64(2),
   381  					"readyReplicas":       int64(2),
   382  					"unavailableReplicas": int64(0),
   383  				},
   384  			}},
   385  			wantScaling: true,
   386  		},
   387  		{
   388  			name: "should return true if spec replicas and status replicas do not match",
   389  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   390  				"spec": map[string]interface{}{
   391  					"replicas": int64(2),
   392  				},
   393  				"status": map[string]interface{}{
   394  					"replicas":            int64(1),
   395  					"updatedReplicas":     int64(2),
   396  					"readyReplicas":       int64(2),
   397  					"unavailableReplicas": int64(0),
   398  				},
   399  			}},
   400  			wantScaling: true,
   401  		},
   402  		{
   403  			name: "should return true if status updatedReplicas is not set on control plane",
   404  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   405  				"spec": map[string]interface{}{
   406  					"replicas": int64(2),
   407  				},
   408  				"status": map[string]interface{}{
   409  					"replicas":            int64(2),
   410  					"readyReplicas":       int64(2),
   411  					"unavailableReplicas": int64(0),
   412  				},
   413  			}},
   414  			wantScaling: true,
   415  		},
   416  		{
   417  			name: "should return true if spec replicas and status updatedReplicas do not match",
   418  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   419  				"spec": map[string]interface{}{
   420  					"replicas": int64(2),
   421  				},
   422  				"status": map[string]interface{}{
   423  					"replicas":            int64(2),
   424  					"updatedReplicas":     int64(1),
   425  					"readyReplicas":       int64(2),
   426  					"unavailableReplicas": int64(0),
   427  				},
   428  			}},
   429  			wantScaling: true,
   430  		},
   431  		{
   432  			name: "should return true if status readyReplicas is not set on control plane",
   433  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   434  				"spec": map[string]interface{}{
   435  					"replicas": int64(2),
   436  				},
   437  				"status": map[string]interface{}{
   438  					"replicas":            int64(2),
   439  					"updatedReplicas":     int64(2),
   440  					"unavailableReplicas": int64(0),
   441  				},
   442  			}},
   443  			wantScaling: true,
   444  		},
   445  		{
   446  			name: "should return true if spec replicas and status readyReplicas do not match",
   447  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   448  				"spec": map[string]interface{}{
   449  					"replicas": int64(2),
   450  				},
   451  				"status": map[string]interface{}{
   452  					"replicas":            int64(2),
   453  					"updatedReplicas":     int64(2),
   454  					"readyReplicas":       int64(1),
   455  					"unavailableReplicas": int64(0),
   456  				},
   457  			}},
   458  			wantScaling: true,
   459  		},
   460  		{
   461  			name: "should return false if status unavailableReplicas is not set on control plane",
   462  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   463  				"spec": map[string]interface{}{
   464  					"replicas": int64(2),
   465  				},
   466  				"status": map[string]interface{}{
   467  					"replicas":        int64(2),
   468  					"updatedReplicas": int64(2),
   469  					"readyReplicas":   int64(2),
   470  				},
   471  			}},
   472  			wantScaling: false,
   473  		},
   474  		{
   475  			name: "should return true if status unavailableReplicas is > 0",
   476  			obj: &unstructured.Unstructured{Object: map[string]interface{}{
   477  				"spec": map[string]interface{}{
   478  					"replicas": int64(2),
   479  				},
   480  				"status": map[string]interface{}{
   481  					"replicas":            int64(2),
   482  					"updatedReplicas":     int64(2),
   483  					"readyReplicas":       int64(2),
   484  					"unavailableReplicas": int64(1),
   485  				},
   486  			}},
   487  			wantScaling: true,
   488  		},
   489  	}
   490  	for _, tt := range tests {
   491  		t.Run(tt.name, func(t *testing.T) {
   492  			g := NewWithT(t)
   493  
   494  			actual, err := ControlPlane().IsScaling(tt.obj)
   495  			g.Expect(err).ToNot(HaveOccurred())
   496  			g.Expect(actual).To(Equal(tt.wantScaling))
   497  		})
   498  	}
   499  }