@@ -20,15 +20,18 @@ import (
2020 "context"
2121 "errors"
2222 "fmt"
23- "os"
24- "path/filepath"
25- "strings"
26-
2723 . "github.com/onsi/ginkgo"
2824 . "github.com/onsi/gomega"
29-
3025 corev1 "k8s.io/api/core/v1"
26+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3127 "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"
3235
3336 "sigs.k8s.io/cluster-api/test/framework"
3437 "sigs.k8s.io/cluster-api/test/framework/clusterctl"
4548
4649// InvalidResourceSpec implements a test that verifies that creating a new cluster fails when the specified resource does not exist
4750func InvalidResourceSpec (ctx context.Context , inputGetter func () CommonSpecInput ) {
48-
4951 BeforeEach (func () {
5052 Expect (ctx ).NotTo (BeNil (), "ctx is required for %s spec" , specName )
5153 input = inputGetter ()
@@ -97,6 +99,65 @@ func InvalidResourceSpec(ctx context.Context, inputGetter func() CommonSpecInput
9799 testInvalidResource (ctx , input , "invalid-disk-offering-size-for-customized" , "is customized, disk size can not be 0 GB" )
98100 })
99101
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+
100161 AfterEach (func () {
101162 // Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
102163 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
105166}
106167
107168func 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 ( )
110171
111172 By ("Configuring a cluster" )
112173 workloadClusterTemplate := clusterctl .ConfigCluster (ctx , clusterctl.ConfigClusterInput {
@@ -131,27 +192,116 @@ func testInvalidResource(ctx context.Context, input CommonSpecInput, flavor stri
131192 Namespace : namespace .Name ,
132193 })
133194
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 ) {
143227 Byf ("Found %q error" , expectedError )
144- return errors . New ( "expected error found" )
228+ return expectedErrorFound
145229 }
146230 }
147- return nil
148- })
149- if err == nil {
150- return "expected error not found" , nil
151- } else {
152- return err .Error (), nil
153231 }
154- }, input .E2EConfig .GetIntervals (specName , "wait-errors" )... ).Should (Equal (string ("expected error found" )))
155232
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
157307}
0 commit comments