sigs.k8s.io/cluster-api@v1.7.1/exp/topology/scope/blueprint_test.go (about)

     1  /*
     2  Copyright 2022 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 scope
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	. "github.com/onsi/gomega"
    24  	corev1 "k8s.io/api/core/v1"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/util/intstr"
    28  	"k8s.io/utils/ptr"
    29  
    30  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    31  	"sigs.k8s.io/cluster-api/internal/test/builder"
    32  )
    33  
    34  func TestIsControlPlaneMachineHealthCheckEnabled(t *testing.T) {
    35  	tests := []struct {
    36  		name      string
    37  		blueprint *ClusterBlueprint
    38  		want      bool
    39  	}{
    40  		{
    41  			name: "should return false if the control plane does not have infrastructure machine",
    42  			blueprint: &ClusterBlueprint{
    43  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
    44  					Build(),
    45  			},
    46  			want: false,
    47  		},
    48  		{
    49  			name: "should return false if no MachineHealthCheck is defined in ClusterClass or ClusterTopology",
    50  			blueprint: &ClusterBlueprint{
    51  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
    52  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
    53  					Build(),
    54  				Topology: builder.ClusterTopology().
    55  					WithClass("cluster-class").
    56  					Build(),
    57  			},
    58  			want: false,
    59  		},
    60  		{
    61  			name: "should return true if MachineHealthCheck if defined in ClusterClass, not defined in cluster topology and enable is not set",
    62  			blueprint: &ClusterBlueprint{
    63  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
    64  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
    65  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckClass{}).
    66  					Build(),
    67  				Topology: builder.ClusterTopology().
    68  					WithClass("cluster-class").
    69  					Build(),
    70  			},
    71  			want: true,
    72  		},
    73  		{
    74  			name: "should return false if MachineHealthCheck if defined in ClusterClass, not defined in cluster topology and enable is false",
    75  			blueprint: &ClusterBlueprint{
    76  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
    77  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
    78  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckClass{}).
    79  					Build(),
    80  				Topology: builder.ClusterTopology().
    81  					WithClass("cluster-class").
    82  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
    83  						Enable: ptr.To(false),
    84  					}).
    85  					Build(),
    86  			},
    87  			want: false,
    88  		},
    89  		{
    90  			name: "should return true if MachineHealthCheck if defined in ClusterClass, not defined in cluster topology and enable is true",
    91  			blueprint: &ClusterBlueprint{
    92  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
    93  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
    94  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckClass{}).
    95  					Build(),
    96  				Topology: builder.ClusterTopology().
    97  					WithClass("cluster-class").
    98  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
    99  						Enable: ptr.To(true),
   100  					}).
   101  					Build(),
   102  			},
   103  			want: true,
   104  		},
   105  		{
   106  			name: "should return true if MachineHealthCheck if defined in cluster topology, not defined in ClusterClass and enable is not set",
   107  			blueprint: &ClusterBlueprint{
   108  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
   109  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
   110  					Build(),
   111  				Topology: builder.ClusterTopology().
   112  					WithClass("cluster-class").
   113  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
   114  						MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   115  							UnhealthyConditions: []clusterv1.UnhealthyCondition{
   116  								{
   117  									Type:    corev1.NodeReady,
   118  									Status:  corev1.ConditionUnknown,
   119  									Timeout: metav1.Duration{Duration: 5 * time.Minute},
   120  								},
   121  							},
   122  						},
   123  					}).
   124  					Build(),
   125  			},
   126  			want: true,
   127  		},
   128  		{
   129  			name: "should return false if MachineHealthCheck if defined in cluster topology, not defined in ClusterClass and enable is false",
   130  			blueprint: &ClusterBlueprint{
   131  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
   132  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
   133  					Build(),
   134  				Topology: builder.ClusterTopology().
   135  					WithClass("cluster-class").
   136  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
   137  						Enable: ptr.To(false),
   138  						MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   139  							UnhealthyConditions: []clusterv1.UnhealthyCondition{
   140  								{
   141  									Type:    corev1.NodeReady,
   142  									Status:  corev1.ConditionUnknown,
   143  									Timeout: metav1.Duration{Duration: 5 * time.Minute},
   144  								},
   145  							},
   146  						},
   147  					}).
   148  					Build(),
   149  			},
   150  			want: false,
   151  		},
   152  		{
   153  			name: "should return true if MachineHealthCheck if defined in cluster topology, not defined in ClusterClass and enable is true",
   154  			blueprint: &ClusterBlueprint{
   155  				ClusterClass: builder.ClusterClass(metav1.NamespaceDefault, "cluster-class").
   156  					WithControlPlaneInfrastructureMachineTemplate(&unstructured.Unstructured{}).
   157  					Build(),
   158  				Topology: builder.ClusterTopology().
   159  					WithClass("cluster-class").
   160  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
   161  						Enable: ptr.To(true),
   162  						MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   163  							UnhealthyConditions: []clusterv1.UnhealthyCondition{
   164  								{
   165  									Type:    corev1.NodeReady,
   166  									Status:  corev1.ConditionUnknown,
   167  									Timeout: metav1.Duration{Duration: 5 * time.Minute},
   168  								},
   169  							},
   170  						},
   171  					}).
   172  					Build(),
   173  			},
   174  			want: true,
   175  		},
   176  	}
   177  
   178  	for _, tt := range tests {
   179  		t.Run(tt.name, func(t *testing.T) {
   180  			g := NewWithT(t)
   181  			g.Expect(tt.blueprint.IsControlPlaneMachineHealthCheckEnabled()).To(Equal(tt.want))
   182  		})
   183  	}
   184  }
   185  
   186  func TestControlPlaneMachineHealthCheckClass(t *testing.T) {
   187  	mhcInClusterClass := &clusterv1.MachineHealthCheckClass{
   188  		UnhealthyConditions: []clusterv1.UnhealthyCondition{
   189  			{
   190  				Type:    corev1.NodeReady,
   191  				Status:  corev1.ConditionFalse,
   192  				Timeout: metav1.Duration{Duration: 10 * time.Minute},
   193  			},
   194  		},
   195  	}
   196  
   197  	percent50 := intstr.FromString("50%")
   198  	mhcInClusterTopology := &clusterv1.MachineHealthCheckClass{
   199  		UnhealthyConditions: []clusterv1.UnhealthyCondition{
   200  			{
   201  				Type:    corev1.NodeReady,
   202  				Status:  corev1.ConditionFalse,
   203  				Timeout: metav1.Duration{Duration: 20 * time.Minute},
   204  			},
   205  		},
   206  		MaxUnhealthy: &percent50,
   207  	}
   208  
   209  	tests := []struct {
   210  		name      string
   211  		blueprint *ClusterBlueprint
   212  		want      *clusterv1.MachineHealthCheckClass
   213  	}{
   214  		{
   215  			name: "should return the MachineHealthCheck from cluster topology if defined - should take precedence over MachineHealthCheck in ClusterClass",
   216  			blueprint: &ClusterBlueprint{
   217  				Topology: builder.ClusterTopology().
   218  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{
   219  						MachineHealthCheckClass: *mhcInClusterTopology,
   220  					}).
   221  					Build(),
   222  				ControlPlane: &ControlPlaneBlueprint{
   223  					MachineHealthCheck: mhcInClusterClass,
   224  				},
   225  			},
   226  			want: mhcInClusterTopology,
   227  		},
   228  		{
   229  			name: "should return the MachineHealthCheck from ClusterClass if no MachineHealthCheck is defined in cluster topology",
   230  			blueprint: &ClusterBlueprint{
   231  				Topology: builder.ClusterTopology().
   232  					WithControlPlaneMachineHealthCheck(&clusterv1.MachineHealthCheckTopology{}).
   233  					Build(),
   234  				ControlPlane: &ControlPlaneBlueprint{
   235  					MachineHealthCheck: mhcInClusterClass,
   236  				},
   237  			},
   238  			want: mhcInClusterClass,
   239  		},
   240  	}
   241  
   242  	for _, tt := range tests {
   243  		t.Run(tt.name, func(t *testing.T) {
   244  			g := NewWithT(t)
   245  			g.Expect(tt.blueprint.ControlPlaneMachineHealthCheckClass()).To(BeComparableTo(tt.want))
   246  		})
   247  	}
   248  }
   249  
   250  func TestIsMachineDeploymentMachineHealthCheckEnabled(t *testing.T) {
   251  	tests := []struct {
   252  		name       string
   253  		blueprint  *ClusterBlueprint
   254  		mdTopology *clusterv1.MachineDeploymentTopology
   255  		want       bool
   256  	}{
   257  		{
   258  			name: "should return false if MachineHealthCheck is not defined in ClusterClass and cluster topology",
   259  			blueprint: &ClusterBlueprint{
   260  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   261  					"worker-class": {},
   262  				},
   263  			},
   264  			mdTopology: &clusterv1.MachineDeploymentTopology{
   265  				Class: "worker-class",
   266  			},
   267  			want: false,
   268  		},
   269  		{
   270  			name: "should return true if MachineHealthCheck is defined in ClusterClass and enable is not set",
   271  			blueprint: &ClusterBlueprint{
   272  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   273  					"worker-class": {
   274  						MachineHealthCheck: &clusterv1.MachineHealthCheckClass{},
   275  					},
   276  				},
   277  			},
   278  			mdTopology: &clusterv1.MachineDeploymentTopology{
   279  				Class: "worker-class",
   280  			},
   281  			want: true,
   282  		},
   283  		{
   284  			name: "should return false if MachineHealthCheck is defined in ClusterClass and enable is false",
   285  			blueprint: &ClusterBlueprint{
   286  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   287  					"worker-class": {
   288  						MachineHealthCheck: &clusterv1.MachineHealthCheckClass{},
   289  					},
   290  				},
   291  			},
   292  			mdTopology: &clusterv1.MachineDeploymentTopology{
   293  				Class: "worker-class",
   294  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   295  					Enable: ptr.To(false),
   296  				},
   297  			},
   298  			want: false,
   299  		},
   300  		{
   301  			name: "should return true if MachineHealthCheck is defined in ClusterClass and enable is true",
   302  			blueprint: &ClusterBlueprint{
   303  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   304  					"worker-class": {
   305  						MachineHealthCheck: &clusterv1.MachineHealthCheckClass{},
   306  					},
   307  				},
   308  			},
   309  			mdTopology: &clusterv1.MachineDeploymentTopology{
   310  				Class: "worker-class",
   311  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   312  					Enable: ptr.To(true),
   313  				},
   314  			},
   315  			want: true,
   316  		},
   317  		{
   318  			name: "should return true if MachineHealthCheck is defined in cluster topology and enable is not set",
   319  			blueprint: &ClusterBlueprint{
   320  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   321  					"worker-class": {},
   322  				},
   323  			},
   324  			mdTopology: &clusterv1.MachineDeploymentTopology{
   325  				Class: "worker-class",
   326  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   327  					MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   328  						UnhealthyConditions: []clusterv1.UnhealthyCondition{
   329  							{
   330  								Type:    corev1.NodeReady,
   331  								Status:  corev1.ConditionUnknown,
   332  								Timeout: metav1.Duration{Duration: 5 * time.Minute},
   333  							},
   334  						},
   335  					},
   336  				},
   337  			},
   338  			want: true,
   339  		},
   340  		{
   341  			name: "should return false if MachineHealthCheck is defined in cluster topology and enable is false",
   342  			blueprint: &ClusterBlueprint{
   343  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   344  					"worker-class": {},
   345  				},
   346  			},
   347  			mdTopology: &clusterv1.MachineDeploymentTopology{
   348  				Class: "worker-class",
   349  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   350  					Enable: ptr.To(false),
   351  					MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   352  						UnhealthyConditions: []clusterv1.UnhealthyCondition{
   353  							{
   354  								Type:    corev1.NodeReady,
   355  								Status:  corev1.ConditionUnknown,
   356  								Timeout: metav1.Duration{Duration: 5 * time.Minute},
   357  							},
   358  						},
   359  					},
   360  				},
   361  			},
   362  			want: false,
   363  		},
   364  		{
   365  			name: "should return true if MachineHealthCheck is defined in cluster topology and enable is true",
   366  			blueprint: &ClusterBlueprint{
   367  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   368  					"worker-class": {},
   369  				},
   370  			},
   371  			mdTopology: &clusterv1.MachineDeploymentTopology{
   372  				Class: "worker-class",
   373  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   374  					Enable: ptr.To(true),
   375  					MachineHealthCheckClass: clusterv1.MachineHealthCheckClass{
   376  						UnhealthyConditions: []clusterv1.UnhealthyCondition{
   377  							{
   378  								Type:    corev1.NodeReady,
   379  								Status:  corev1.ConditionUnknown,
   380  								Timeout: metav1.Duration{Duration: 5 * time.Minute},
   381  							},
   382  						},
   383  					},
   384  				},
   385  			},
   386  			want: true,
   387  		},
   388  	}
   389  
   390  	for _, tt := range tests {
   391  		t.Run(tt.name, func(t *testing.T) {
   392  			g := NewWithT(t)
   393  			g.Expect(tt.blueprint.IsMachineDeploymentMachineHealthCheckEnabled(tt.mdTopology)).To(BeComparableTo(tt.want))
   394  		})
   395  	}
   396  }
   397  
   398  func TestMachineDeploymentMachineHealthCheckClass(t *testing.T) {
   399  	mhcInClusterClass := &clusterv1.MachineHealthCheckClass{
   400  		UnhealthyConditions: []clusterv1.UnhealthyCondition{
   401  			{
   402  				Type:    corev1.NodeReady,
   403  				Status:  corev1.ConditionFalse,
   404  				Timeout: metav1.Duration{Duration: 10 * time.Minute},
   405  			},
   406  		},
   407  	}
   408  
   409  	percent50 := intstr.FromString("50%")
   410  	mhcInClusterTopology := &clusterv1.MachineHealthCheckClass{
   411  		UnhealthyConditions: []clusterv1.UnhealthyCondition{
   412  			{
   413  				Type:    corev1.NodeReady,
   414  				Status:  corev1.ConditionFalse,
   415  				Timeout: metav1.Duration{Duration: 20 * time.Minute},
   416  			},
   417  		},
   418  		MaxUnhealthy: &percent50,
   419  	}
   420  
   421  	tests := []struct {
   422  		name       string
   423  		blueprint  *ClusterBlueprint
   424  		mdTopology *clusterv1.MachineDeploymentTopology
   425  		want       *clusterv1.MachineHealthCheckClass
   426  	}{
   427  		{
   428  			name: "should return the MachineHealthCheck from cluster topology if defined - should take precedence over MachineHealthCheck in ClusterClass",
   429  			blueprint: &ClusterBlueprint{
   430  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   431  					"worker-class": {
   432  						MachineHealthCheck: mhcInClusterClass,
   433  					},
   434  				},
   435  			},
   436  			mdTopology: &clusterv1.MachineDeploymentTopology{
   437  				Class: "worker-class",
   438  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{
   439  					MachineHealthCheckClass: *mhcInClusterTopology,
   440  				},
   441  			},
   442  			want: mhcInClusterTopology,
   443  		},
   444  		{
   445  			name: "should return the MachineHealthCheck from ClusterClass if no MachineHealthCheck is defined in cluster topology",
   446  			blueprint: &ClusterBlueprint{
   447  				MachineDeployments: map[string]*MachineDeploymentBlueprint{
   448  					"worker-class": {
   449  						MachineHealthCheck: mhcInClusterClass,
   450  					},
   451  				},
   452  			},
   453  			mdTopology: &clusterv1.MachineDeploymentTopology{
   454  				Class:              "worker-class",
   455  				MachineHealthCheck: &clusterv1.MachineHealthCheckTopology{},
   456  			},
   457  			want: mhcInClusterClass,
   458  		},
   459  	}
   460  
   461  	for _, tt := range tests {
   462  		t.Run(tt.name, func(t *testing.T) {
   463  			g := NewWithT(t)
   464  			g.Expect(tt.blueprint.MachineDeploymentMachineHealthCheckClass(tt.mdTopology)).To(BeComparableTo(tt.want))
   465  		})
   466  	}
   467  }