@@ -20,15 +20,18 @@ import (
20
20
"context"
21
21
"errors"
22
22
"fmt"
23
- "os"
24
- "path/filepath"
25
- "strings"
26
-
27
23
. "github.com/onsi/ginkgo"
28
24
. "github.com/onsi/gomega"
29
-
30
25
corev1 "k8s.io/api/core/v1"
26
+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
31
27
"k8s.io/utils/pointer"
28
+ "os"
29
+ "path/filepath"
30
+ "sigs.k8s.io/cluster-api/api/v1beta1"
31
+ controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1"
32
+ "sigs.k8s.io/cluster-api/util/patch"
33
+ "sigs.k8s.io/controller-runtime/pkg/client"
34
+ "strings"
32
35
33
36
"sigs.k8s.io/cluster-api/test/framework"
34
37
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
45
48
46
49
// InvalidResourceSpec implements a test that verifies that creating a new cluster fails when the specified resource does not exist
47
50
func InvalidResourceSpec (ctx context.Context , inputGetter func () CommonSpecInput ) {
48
-
49
51
BeforeEach (func () {
50
52
Expect (ctx ).NotTo (BeNil (), "ctx is required for %s spec" , specName )
51
53
input = inputGetter ()
@@ -97,6 +99,65 @@ func InvalidResourceSpec(ctx context.Context, inputGetter func() CommonSpecInput
97
99
testInvalidResource (ctx , input , "invalid-disk-offering-size-for-customized" , "is customized, disk size can not be 0 GB" )
98
100
})
99
101
102
+ Context ("When starting with a healthy cluster" , func () {
103
+ var logFolder string
104
+
105
+ BeforeEach (func () {
106
+ logFolder = generateLogFolderPath ()
107
+
108
+ By ("Creating a workload cluster" )
109
+ clusterctl .ApplyClusterTemplateAndWait (ctx , clusterctl.ApplyClusterTemplateAndWaitInput {
110
+ ClusterProxy : input .BootstrapClusterProxy ,
111
+ CNIManifestPath : input .E2EConfig .GetVariable (CNIPath ),
112
+ ConfigCluster : clusterctl.ConfigClusterInput {
113
+ LogFolder : logFolder ,
114
+ ClusterctlConfigPath : input .ClusterctlConfigPath ,
115
+ KubeconfigPath : input .BootstrapClusterProxy .GetKubeconfigPath (),
116
+ InfrastructureProvider : clusterctl .DefaultInfrastructureProvider ,
117
+ Flavor : "insufficient-compute-resources-for-upgrade" ,
118
+ Namespace : namespace .Name ,
119
+ ClusterName : generateClusterName (),
120
+ KubernetesVersion : input .E2EConfig .GetVariable (KubernetesVersion ),
121
+ ControlPlaneMachineCount : pointer .Int64Ptr (1 ),
122
+ WorkerMachineCount : pointer .Int64Ptr (1 ),
123
+ },
124
+ WaitForClusterIntervals : input .E2EConfig .GetIntervals (specName , "wait-cluster" ),
125
+ WaitForControlPlaneIntervals : input .E2EConfig .GetIntervals (specName , "wait-control-plane" ),
126
+ WaitForMachineDeployments : input .E2EConfig .GetIntervals (specName , "wait-worker-nodes" ),
127
+ }, clusterResources )
128
+ })
129
+
130
+ It ("Should fail to upgrade worker machine due to insufficient compute resources" , func () {
131
+ By ("Making sure the expected error didn't occur yet" )
132
+ expectedError := "Unable to create a deployment for VM"
133
+ Expect (errorExistsInLog (logFolder , expectedError )).To (BeFalse ())
134
+
135
+ By ("Increasing the machine deployment instance size" )
136
+ deployment := clusterResources .MachineDeployments [0 ]
137
+ deployment .Spec .Template .Spec .InfrastructureRef .Name =
138
+ strings .Replace (deployment .Spec .Template .Spec .InfrastructureRef .Name , "-md-0" , "-upgrade-md-0" , 1 )
139
+ upgradeMachineDeploymentInfrastructureRef (ctx , deployment )
140
+
141
+ By ("Checking for the expected error" )
142
+ waitForErrorInLog (logFolder , expectedError )
143
+ })
144
+
145
+ It ("Should fail to upgrade control plane machine due to insufficient compute resources" , func () {
146
+ By ("Making sure the expected error didn't occur yet" )
147
+ expectedError := "Unable to create a deployment for VM"
148
+ Expect (errorExistsInLog (logFolder , expectedError )).To (BeFalse ())
149
+
150
+ By ("Increasing the machine deployment instance size" )
151
+ cp := clusterResources .ControlPlane
152
+ cp .Spec .MachineTemplate .InfrastructureRef .Name =
153
+ strings .Replace (cp .Spec .MachineTemplate .InfrastructureRef .Name , "-control-plane" , "-upgrade-control-plane" , 1 )
154
+ upgradeControlPlaneInfrastructureRef (ctx , cp )
155
+
156
+ By ("Checking for the expected error" )
157
+ waitForErrorInLog (logFolder , expectedError )
158
+ })
159
+ })
160
+
100
161
AfterEach (func () {
101
162
// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
102
163
dumpSpecResourcesAndCleanup (ctx , specName , input .BootstrapClusterProxy , input .ArtifactFolder , namespace , cancelWatches , clusterResources .Cluster , input .E2EConfig .GetIntervals , input .SkipCleanup )
@@ -105,8 +166,8 @@ func InvalidResourceSpec(ctx context.Context, inputGetter func() CommonSpecInput
105
166
}
106
167
107
168
func testInvalidResource (ctx context.Context , input CommonSpecInput , flavor string , expectedError string ) {
108
- logFolder := filepath . Join ( input . ArtifactFolder , "clusters" , input . BootstrapClusterProxy . GetName () )
109
- clusterName := fmt . Sprintf ( "%s-%s" , specName , util . RandomString ( 6 ) )
169
+ logFolder := generateLogFolderPath ( )
170
+ clusterName := generateClusterName ( )
110
171
111
172
By ("Configuring a cluster" )
112
173
workloadClusterTemplate := clusterctl .ConfigCluster (ctx , clusterctl.ConfigClusterInput {
@@ -131,27 +192,116 @@ func testInvalidResource(ctx context.Context, input CommonSpecInput, flavor stri
131
192
Namespace : namespace .Name ,
132
193
})
133
194
134
- Byf ("Waiting for %q error to occur" , expectedError )
135
- Eventually (func () (string , error ) {
136
- err := filepath .Walk (logFolder , func (path string , info os.FileInfo , err error ) error {
137
- if err != nil {
138
- return err
139
- }
140
- if strings .Contains (path , "capc-controller-manager" ) && strings .Contains (path , "manager.log" ) {
141
- log , _ := os .ReadFile (path )
142
- if strings .Contains (string (log ), expectedError ) {
195
+ waitForErrorInLog (logFolder , expectedError )
196
+
197
+ By ("PASSED!" )
198
+ }
199
+
200
+ func generateLogFolderPath () string {
201
+ return filepath .Join (input .ArtifactFolder , "clusters" , input .BootstrapClusterProxy .GetName ())
202
+ }
203
+
204
+ func generateClusterName () string {
205
+ return fmt .Sprintf ("%s-%s" , specName , util .RandomString (6 ))
206
+ }
207
+
208
+ // errorExistsInLog looks for a specific error message in the CAPC controller log files. Because the logs may contain
209
+ // entries from previous test runs, or from previous tests in the same run, the function also requires that the log
210
+ // line contains the namespace and cluster names.
211
+ func errorExistsInLog (logFolder string , expectedError string ) (bool , error ) {
212
+ expectedErrorFound := errors .New ("expected error found" )
213
+ controllerLogPath := filepath .Join (logFolder , "controllers" , "capc-controller-manager" )
214
+
215
+ err := filepath .Walk (controllerLogPath , func (path string , info os.FileInfo , err error ) error {
216
+ if err != nil {
217
+ return err
218
+ }
219
+
220
+ if strings .Contains (path , "manager.log" ) {
221
+ log , _ := os .ReadFile (path )
222
+ logLines := strings .Split (string (log ), "\n " )
223
+ for _ , line := range logLines {
224
+ if strings .Contains (line , expectedError ) &&
225
+ strings .Contains (line , clusterResources .Cluster .Namespace ) &&
226
+ strings .Contains (line , clusterResources .Cluster .Name ) {
143
227
Byf ("Found %q error" , expectedError )
144
- return errors . New ( "expected error found" )
228
+ return expectedErrorFound
145
229
}
146
230
}
147
- return nil
148
- })
149
- if err == nil {
150
- return "expected error not found" , nil
151
- } else {
152
- return err .Error (), nil
153
231
}
154
- }, input .E2EConfig .GetIntervals (specName , "wait-errors" )... ).Should (Equal (string ("expected error found" )))
155
232
156
- By ("PASSED!" )
233
+ return nil
234
+ })
235
+
236
+ if err == nil {
237
+ return false , nil
238
+ } else if err == expectedErrorFound {
239
+ return true , nil
240
+ }
241
+ return false , err
242
+ }
243
+
244
+ func waitForErrorInLog (logFolder string , expectedError string ) {
245
+ Byf ("Waiting for %q error to occur" , expectedError )
246
+ Eventually (func () (bool , error ) {
247
+ return errorExistsInLog (logFolder , expectedError )
248
+ }, input .E2EConfig .GetIntervals (specName , "wait-errors" )... ).Should (BeTrue ())
249
+ }
250
+
251
+ // upgradeMachineDeploymentInfrastructureRef updates a machine deployment infrastructure ref and returns immediately.
252
+ // The logic was borrowed from framework.UpgradeMachineDeploymentInfrastructureRefAndWait.
253
+ func upgradeMachineDeploymentInfrastructureRef (ctx context.Context , deployment * v1beta1.MachineDeployment ) {
254
+ By ("Patching the machine deployment infrastructure ref" )
255
+ mgmtClient := input .BootstrapClusterProxy .GetClient ()
256
+
257
+ // Create a new infrastructure ref based on the existing one
258
+ infraRef := deployment .Spec .Template .Spec .InfrastructureRef
259
+ newInfraObjName := createNewInfrastructureRef (ctx , infraRef )
260
+
261
+ // Patch the new infra object's ref to the machine deployment
262
+ patchHelper , err := patch .NewHelper (deployment , mgmtClient )
263
+ Expect (err ).ToNot (HaveOccurred ())
264
+ infraRef .Name = newInfraObjName
265
+ deployment .Spec .Template .Spec .InfrastructureRef = infraRef
266
+ Expect (patchHelper .Patch (ctx , deployment )).To (Succeed ())
267
+ }
268
+
269
+ // upgradeControlPlane upgrades a control plane deployment infrastructure ref and returns immediately.
270
+ func upgradeControlPlaneInfrastructureRef (ctx context.Context , controlPlane * controlplanev1.KubeadmControlPlane ) {
271
+ By ("Patching the control plane infrastructure ref" )
272
+ mgmtClient := input .BootstrapClusterProxy .GetClient ()
273
+
274
+ // Create a new infrastructure ref based on the existing one
275
+ infraRef := controlPlane .Spec .MachineTemplate .InfrastructureRef
276
+ newInfraObjName := createNewInfrastructureRef (ctx , infraRef )
277
+
278
+ // Patch the control plane to use the new infrastructure ref
279
+ patchHelper , err := patch .NewHelper (controlPlane , mgmtClient )
280
+ Expect (err ).ToNot (HaveOccurred ())
281
+ infraRef .Name = newInfraObjName
282
+ controlPlane .Spec .MachineTemplate .InfrastructureRef = infraRef
283
+ Expect (patchHelper .Patch (ctx , controlPlane )).To (Succeed ())
284
+ }
285
+
286
+ // createNewInfrastructureRef creates a new infrastructure ref that's based on an existing one, but has a new name. The
287
+ // new name is returned.
288
+ func createNewInfrastructureRef (ctx context.Context , sourceInfrastructureRef corev1.ObjectReference ) string {
289
+ mgmtClient := input .BootstrapClusterProxy .GetClient ()
290
+
291
+ // Retrieve the existing infrastructure ref object
292
+ infraObj := & unstructured.Unstructured {}
293
+ infraObj .SetGroupVersionKind (sourceInfrastructureRef .GroupVersionKind ())
294
+ key := client.ObjectKey {
295
+ Namespace : clusterResources .Cluster .Namespace ,
296
+ Name : sourceInfrastructureRef .Name ,
297
+ }
298
+ Expect (mgmtClient .Get (ctx , key , infraObj )).NotTo (HaveOccurred ())
299
+
300
+ // Creates a new infrastructure ref object
301
+ newInfraObj := infraObj
302
+ newInfraObjName := fmt .Sprintf ("%s-%s" , sourceInfrastructureRef .Name , util .RandomString (6 ))
303
+ newInfraObj .SetName (newInfraObjName )
304
+ newInfraObj .SetResourceVersion ("" )
305
+ Expect (mgmtClient .Create (ctx , newInfraObj )).NotTo (HaveOccurred ())
306
+ return newInfraObjName
157
307
}
0 commit comments