@@ -18,23 +18,22 @@ package e2e
18
18
19
19
import (
20
20
"context"
21
- "errors"
22
21
"fmt"
23
22
"os"
24
23
"path/filepath"
24
+ "strings"
25
25
26
26
. "github.com/onsi/ginkgo/v2"
27
27
. "github.com/onsi/gomega"
28
28
corev1 "k8s.io/api/core/v1"
29
29
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30
- "k8s.io/klog/v2"
31
30
"k8s.io/utils/pointer"
32
31
"sigs.k8s.io/controller-runtime/pkg/client"
33
32
34
33
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
34
+ clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
35
35
"sigs.k8s.io/cluster-api/test/e2e/internal/log"
36
36
"sigs.k8s.io/cluster-api/test/framework"
37
- "sigs.k8s.io/cluster-api/test/framework/bootstrap"
38
37
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
39
38
"sigs.k8s.io/cluster-api/util"
40
39
)
@@ -48,6 +47,22 @@ type SelfHostedSpecInput struct {
48
47
SkipCleanup bool
49
48
ControlPlaneWaiters clusterctl.ControlPlaneWaiters
50
49
Flavor string
50
+
51
+ // SkipUpgrade skip the upgrade of the self-hosted clusters kubernetes version.
52
+ // If true, the variable KUBERNETES_VERSION is expected to be set.
53
+ // If false, the variables KUBERNETES_VERSION_UPGRADE_FROM, KUBERNETES_VERSION_UPGRADE_TO,
54
+ // ETCD_VERSION_UPGRADE_TO and COREDNS_VERSION_UPGRADE_TO are expected to be set.
55
+ SkipUpgrade bool
56
+
57
+ // ControlPlaneMachineCount is used in `config cluster` to configure the count of the control plane machines used in the test.
58
+ // Default is 1.
59
+ ControlPlaneMachineCount * int64
60
+
61
+ // WorkerMachineCount is used in `config cluster` to configure the count of the worker machines used in the test.
62
+ // NOTE: If the WORKER_MACHINE_COUNT var is used multiple times in the cluster template, the absolute count of
63
+ // worker machines is a multiple of WorkerMachineCount.
64
+ // Default is 1.
65
+ WorkerMachineCount * int64
51
66
}
52
67
53
68
// SelfHostedSpec implements a test that verifies Cluster API creating a cluster, pivoting to a self-hosted cluster.
@@ -64,6 +79,11 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
64
79
selfHostedNamespace * corev1.Namespace
65
80
selfHostedCancelWatches context.CancelFunc
66
81
selfHostedCluster * clusterv1.Cluster
82
+
83
+ controlPlaneMachineCount int64
84
+ workerMachineCount int64
85
+
86
+ kubernetesVersion string
67
87
)
68
88
69
89
BeforeEach (func () {
@@ -73,17 +93,60 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
73
93
Expect (input .ClusterctlConfigPath ).To (BeAnExistingFile (), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec" , specName )
74
94
Expect (input .BootstrapClusterProxy ).ToNot (BeNil (), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec" , specName )
75
95
Expect (os .MkdirAll (input .ArtifactFolder , 0750 )).To (Succeed (), "Invalid argument. input.ArtifactFolder can't be created for %s spec" , specName )
76
- Expect (input .E2EConfig .Variables ).To (HaveKey (KubernetesVersion ))
96
+
97
+ if input .SkipUpgrade {
98
+ // Use KubernetesVersion if no upgrade step is defined by test input.
99
+ Expect (input .E2EConfig .Variables ).To (HaveKey (KubernetesVersion ))
100
+
101
+ kubernetesVersion = input .E2EConfig .GetVariable (KubernetesVersion )
102
+ } else {
103
+ Expect (input .E2EConfig .Variables ).To (HaveKey (KubernetesVersionUpgradeFrom ))
104
+ Expect (input .E2EConfig .Variables ).To (HaveKey (KubernetesVersionUpgradeTo ))
105
+ Expect (input .E2EConfig .Variables ).To (HaveKey (EtcdVersionUpgradeTo ))
106
+ Expect (input .E2EConfig .Variables ).To (HaveKey (CoreDNSVersionUpgradeTo ))
107
+
108
+ kubernetesVersion = input .E2EConfig .GetVariable (KubernetesVersionUpgradeFrom )
109
+ }
77
110
78
111
// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
79
112
namespace , cancelWatches = setupSpecNamespace (ctx , specName , input .BootstrapClusterProxy , input .ArtifactFolder )
80
113
clusterResources = new (clusterctl.ApplyClusterTemplateAndWaitResult )
114
+
115
+ if input .ControlPlaneMachineCount == nil {
116
+ controlPlaneMachineCount = 1
117
+ } else {
118
+ controlPlaneMachineCount = * input .ControlPlaneMachineCount
119
+ }
120
+
121
+ if input .WorkerMachineCount == nil {
122
+ workerMachineCount = 1
123
+ } else {
124
+ workerMachineCount = * input .WorkerMachineCount
125
+ }
81
126
})
82
127
83
128
It ("Should pivot the bootstrap cluster to a self-hosted cluster" , func () {
84
129
By ("Creating a workload cluster" )
85
130
86
131
workloadClusterName := fmt .Sprintf ("%s-%s" , specName , util .RandomString (6 ))
132
+ clusterctlVariables := map [string ]string {}
133
+
134
+ // In case the infrastructure-docker provider is installed, ensure to add the preload images variable to load the
135
+ // controller images into the nodes.
136
+ // NOTE: we are checking the bootstrap cluster and assuming the workload cluster will be on the same infrastructure provider.
137
+ // Also, given that we use it to set a variable, then it is up to cluster templates to use it or not.
138
+ hasDockerInfrastructureProvider := hasProvider (ctx , input .BootstrapClusterProxy .GetClient (), "infrastructure-docker" )
139
+
140
+ // In case the infrastructure-docker provider is installed, ensure to add the preload images variable to load the
141
+ // controller images into the nodes.
142
+ if hasDockerInfrastructureProvider {
143
+ images := []string {}
144
+ for _ , image := range input .E2EConfig .Images {
145
+ images = append (images , fmt .Sprintf ("%q" , image .Name ))
146
+ }
147
+ clusterctlVariables ["DOCKER_PRELOAD_IMAGES" ] = `[` + strings .Join (images , "," ) + `]`
148
+ }
149
+
87
150
clusterctl .ApplyClusterTemplateAndWait (ctx , clusterctl.ApplyClusterTemplateAndWaitInput {
88
151
ClusterProxy : input .BootstrapClusterProxy ,
89
152
ConfigCluster : clusterctl.ConfigClusterInput {
@@ -94,9 +157,10 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
94
157
Flavor : input .Flavor ,
95
158
Namespace : namespace .Name ,
96
159
ClusterName : workloadClusterName ,
97
- KubernetesVersion : input .E2EConfig .GetVariable (KubernetesVersion ),
98
- ControlPlaneMachineCount : pointer .Int64Ptr (1 ),
99
- WorkerMachineCount : pointer .Int64Ptr (1 ),
160
+ KubernetesVersion : kubernetesVersion ,
161
+ ControlPlaneMachineCount : & controlPlaneMachineCount ,
162
+ WorkerMachineCount : & workerMachineCount ,
163
+ ClusterctlVariables : clusterctlVariables ,
100
164
},
101
165
ControlPlaneWaiters : input .ControlPlaneWaiters ,
102
166
WaitForClusterIntervals : input .E2EConfig .GetIntervals (specName , "wait-cluster" ),
@@ -106,34 +170,7 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
106
170
107
171
By ("Turning the workload cluster into a management cluster" )
108
172
109
- // In case the cluster is a DockerCluster, we should load controller images into the nodes.
110
- // Nb. this can be achieved also by changing the DockerMachine spec, but for the time being we are using
111
- // this approach because this allows to have a single source of truth for images, the e2e config
112
- // Nb. If the cluster is a managed topology cluster.Spec.InfrastructureRef will be nil till
113
- // the cluster object is reconciled. Therefore, we always try to fetch the reconciled cluster object from
114
- // the server to check if it is a DockerCluster.
115
173
cluster := clusterResources .Cluster
116
- isDockerCluster := false
117
- Eventually (func () error {
118
- c := input .BootstrapClusterProxy .GetClient ()
119
- tmpCluster := & clusterv1.Cluster {}
120
- if err := c .Get (ctx , client.ObjectKey {Name : cluster .Name , Namespace : cluster .Namespace }, tmpCluster ); err != nil {
121
- return err
122
- }
123
- if tmpCluster .Spec .InfrastructureRef != nil {
124
- isDockerCluster = tmpCluster .Spec .InfrastructureRef .Kind == "DockerCluster"
125
- return nil
126
- }
127
- return errors .New ("cluster object not yet reconciled" )
128
- }, "1m" , "5s" ).Should (Succeed (), "Failed to get the Cluster %s" , klog .KObj (cluster ))
129
-
130
- if isDockerCluster {
131
- Expect (bootstrap .LoadImagesToKindCluster (ctx , bootstrap.LoadImagesToKindClusterInput {
132
- Name : cluster .Name ,
133
- Images : input .E2EConfig .Images ,
134
- })).To (Succeed ())
135
- }
136
-
137
174
// Get a ClusterBroker so we can interact with the workload cluster
138
175
selfHostedClusterProxy = input .BootstrapClusterProxy .GetWorkloadCluster (ctx , cluster .Namespace , cluster .Name )
139
176
@@ -217,6 +254,94 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
217
254
return matchUnstructuredLists (preMoveMachineList , postMoveMachineList )
218
255
}, "3m" , "30s" ).Should (BeTrue (), "Machines should not roll out after move to self-hosted cluster" )
219
256
257
+ if input .SkipUpgrade {
258
+ // Only do upgrade step if defined by test input.
259
+ return
260
+ }
261
+
262
+ By ("Upgrading the self-hosted Cluster" )
263
+
264
+ if clusterResources .Cluster .Spec .Topology != nil {
265
+ // Cluster is using ClusterClass, upgrade via topology.
266
+ By ("Upgrading the Cluster topology" )
267
+ framework .UpgradeClusterTopologyAndWaitForUpgrade (ctx , framework.UpgradeClusterTopologyAndWaitForUpgradeInput {
268
+ ClusterProxy : selfHostedClusterProxy ,
269
+ Cluster : clusterResources .Cluster ,
270
+ ControlPlane : clusterResources .ControlPlane ,
271
+ EtcdImageTag : input .E2EConfig .GetVariable (EtcdVersionUpgradeTo ),
272
+ DNSImageTag : input .E2EConfig .GetVariable (CoreDNSVersionUpgradeTo ),
273
+ MachineDeployments : clusterResources .MachineDeployments ,
274
+ KubernetesUpgradeVersion : input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ),
275
+ WaitForMachinesToBeUpgraded : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
276
+ WaitForKubeProxyUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
277
+ WaitForDNSUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
278
+ WaitForEtcdUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
279
+ })
280
+ } else {
281
+ // Cluster is not using ClusterClass, upgrade via individual resources.
282
+ By ("Upgrading the Kubernetes control-plane" )
283
+ var (
284
+ upgradeCPMachineTemplateTo * string
285
+ upgradeWorkersMachineTemplateTo * string
286
+ )
287
+
288
+ if input .E2EConfig .HasVariable (CPMachineTemplateUpgradeTo ) {
289
+ upgradeCPMachineTemplateTo = pointer .StringPtr (input .E2EConfig .GetVariable (CPMachineTemplateUpgradeTo ))
290
+ }
291
+
292
+ if input .E2EConfig .HasVariable (WorkersMachineTemplateUpgradeTo ) {
293
+ upgradeWorkersMachineTemplateTo = pointer .StringPtr (input .E2EConfig .GetVariable (WorkersMachineTemplateUpgradeTo ))
294
+ }
295
+
296
+ framework .UpgradeControlPlaneAndWaitForUpgrade (ctx , framework.UpgradeControlPlaneAndWaitForUpgradeInput {
297
+ ClusterProxy : selfHostedClusterProxy ,
298
+ Cluster : clusterResources .Cluster ,
299
+ ControlPlane : clusterResources .ControlPlane ,
300
+ EtcdImageTag : input .E2EConfig .GetVariable (EtcdVersionUpgradeTo ),
301
+ DNSImageTag : input .E2EConfig .GetVariable (CoreDNSVersionUpgradeTo ),
302
+ KubernetesUpgradeVersion : input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ),
303
+ UpgradeMachineTemplate : upgradeCPMachineTemplateTo ,
304
+ WaitForMachinesToBeUpgraded : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
305
+ WaitForKubeProxyUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
306
+ WaitForDNSUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
307
+ WaitForEtcdUpgrade : input .E2EConfig .GetIntervals (specName , "wait-machine-upgrade" ),
308
+ })
309
+
310
+ if workerMachineCount > 0 {
311
+ By ("Upgrading the machine deployment" )
312
+ framework .UpgradeMachineDeploymentsAndWait (ctx , framework.UpgradeMachineDeploymentsAndWaitInput {
313
+ ClusterProxy : selfHostedClusterProxy ,
314
+ Cluster : clusterResources .Cluster ,
315
+ UpgradeVersion : input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ),
316
+ UpgradeMachineTemplate : upgradeWorkersMachineTemplateTo ,
317
+ MachineDeployments : clusterResources .MachineDeployments ,
318
+ WaitForMachinesToBeUpgraded : input .E2EConfig .GetIntervals (specName , "wait-worker-nodes" ),
319
+ })
320
+ }
321
+ }
322
+
323
+ // Only attempt to upgrade MachinePools if they were provided in the template.
324
+ if len (clusterResources .MachinePools ) > 0 && workerMachineCount > 0 {
325
+ By ("Upgrading the machinepool instances" )
326
+ framework .UpgradeMachinePoolAndWait (ctx , framework.UpgradeMachinePoolAndWaitInput {
327
+ ClusterProxy : selfHostedClusterProxy ,
328
+ Cluster : clusterResources .Cluster ,
329
+ UpgradeVersion : input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ),
330
+ WaitForMachinePoolToBeUpgraded : input .E2EConfig .GetIntervals (specName , "wait-machine-pool-upgrade" ),
331
+ MachinePools : clusterResources .MachinePools ,
332
+ })
333
+ }
334
+
335
+ By ("Waiting until nodes are ready" )
336
+ workloadProxy := selfHostedClusterProxy .GetWorkloadCluster (ctx , namespace .Name , clusterResources .Cluster .Name )
337
+ workloadClient := workloadProxy .GetClient ()
338
+ framework .WaitForNodesReady (ctx , framework.WaitForNodesReadyInput {
339
+ Lister : workloadClient ,
340
+ KubernetesVersion : input .E2EConfig .GetVariable (KubernetesVersionUpgradeTo ),
341
+ Count : int (clusterResources .ExpectedTotalNodes ()),
342
+ WaitForNodesReady : input .E2EConfig .GetIntervals (specName , "wait-nodes-ready" ),
343
+ })
344
+
220
345
By ("PASSED!" )
221
346
})
222
347
@@ -267,3 +392,17 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
267
392
dumpSpecResourcesAndCleanup (ctx , specName , input .BootstrapClusterProxy , input .ArtifactFolder , namespace , cancelWatches , clusterResources .Cluster , input .E2EConfig .GetIntervals , input .SkipCleanup )
268
393
})
269
394
}
395
+
396
+ func hasProvider (ctx context.Context , c client.Client , providerName string ) bool {
397
+ providerList := clusterctlv1.ProviderList {}
398
+ Eventually (func () error {
399
+ return c .List (ctx , & providerList )
400
+ }, "1m" , "5s" ).Should (Succeed (), "Failed to list the Providers" )
401
+
402
+ for _ , provider := range providerList .Items {
403
+ if provider .GetName () == providerName {
404
+ return true
405
+ }
406
+ }
407
+ return false
408
+ }
0 commit comments