Skip to content

Commit c0a247b

Browse files
authored
Merge pull request #7239 from chrischdi/pr-self-hosted-upgrades
✨ adjust self-hosted e2e test to also upgrade the cluster
2 parents df0fb87 + 229d15a commit c0a247b

File tree

12 files changed

+265
-43
lines changed

12 files changed

+265
-43
lines changed

docs/book/src/developer/providers/v1.2-to-v1.3.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,9 @@ The default value is 0, meaning that the volume can be detached without any time
5454
- The `Move` function in E2E test framework for clusterctl has been modified to:
5555
* print the `clusterctl move` command including the arguments similar to `Init`.
5656
* log the output to the a `clusterctl-move.log` file at the subdirectory `logs/<namespace>`.
57+
- The self-hosted upgrade test now also upgrades the self-hosted clusters kubernetes version by default. For that it requires the following variables to be set:
58+
* `KUBERNETES_VERSION_UPGRADE_FROM`
59+
* `KUBERNETES_VERSION_UPGRADE_TO`
60+
* `ETCD_VERSION_UPGRADE_TO`
61+
* `COREDNS_VERSION_UPGRADE_TO`
62+
The variable `SkipUpgrade` could be set to revert to the old behaviour by making use of the `KUBERNETES_VERSION` variable and skipping the kubernetes upgrade.

test/e2e/data/infrastructure-docker/v1beta1/main/bases/cluster-with-kcp.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ spec:
5959
extraMounts:
6060
- containerPath: "/var/run/docker.sock"
6161
hostPath: "/var/run/docker.sock"
62+
# The DOCKER_PRELOAD_IMAGES variable gets set in self-hosted E2E tests to the list of images of the E2E configuration.
63+
preLoadImages: ${DOCKER_PRELOAD_IMAGES:-[]}
6264
---
6365
# KubeadmControlPlane referenced by the Cluster object with
6466
# - the label kcp-adoption.step2, because it should be created in the second step of the kcp-adoption test.

test/e2e/data/infrastructure-docker/v1beta1/main/bases/cluster-with-topology.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,7 @@ spec:
3434
value: ""
3535
# We set an empty value to use the default tag kubeadm init is using.
3636
- name: coreDNSImageTag
37-
value: ""
37+
value: ""
38+
- name: preLoadImages
39+
# The DOCKER_PRELOAD_IMAGES variable gets set in self-hosted E2E tests to the list of images of the E2E configuration.
40+
value: ${DOCKER_PRELOAD_IMAGES:-[]}

test/e2e/data/infrastructure-docker/v1beta1/main/bases/md.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ spec:
1111
extraMounts:
1212
- containerPath: "/var/run/docker.sock"
1313
hostPath: "/var/run/docker.sock"
14+
# The DOCKER_PRELOAD_IMAGES variable gets set in self-hosted E2E tests to the list of images of the E2E configuration.
15+
preLoadImages: ${DOCKER_PRELOAD_IMAGES:-[]}
1416
---
1517
# KubeadmConfigTemplate referenced by the MachineDeployment
1618
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1

test/e2e/data/infrastructure-docker/v1beta1/main/bases/mp.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
3232
kind: DockerMachinePool
3333
metadata:
3434
name: "${CLUSTER_NAME}-dmp-0"
35+
spec:
36+
template:
37+
# The DOCKER_PRELOAD_IMAGES variable gets set in self-hosted E2E tests to the list of images of the E2E configuration.
38+
preLoadImages: ${DOCKER_PRELOAD_IMAGES:-[]}
3539
---
3640
# KubeadmConfigTemplate referenced by the MachinePool
3741
apiVersion: bootstrap.cluster.x-k8s.io/v1beta1

test/e2e/data/infrastructure-docker/v1beta1/main/clusterclass-quick-start.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ spec:
7575
default: ""
7676
example: "0"
7777
description: "kubeadmControlPlaneMaxSurge is the maximum number of control planes that can be scheduled above or under the desired number of control plane machines."
78+
- name: preLoadImages
79+
required: false
80+
schema:
81+
openAPIV3Schema:
82+
default: []
83+
type: array
84+
items:
85+
type: string
86+
description: "preLoadImages sets the images for the docker machines to preload."
7887
patches:
7988
- name: lbImageRepository
8089
definitions:
@@ -183,6 +192,25 @@ spec:
183192
valueFrom:
184193
template: |
185194
kindest/node:{{ .builtin.controlPlane.version | replace "+" "_" }}
195+
- name: preloadImages
196+
description: |
197+
Sets the container images to preload to the node that is used for running dockerMachines.
198+
This is especially required for self-hosted e2e tests to ensure the required controller images to be available
199+
and reduce load to public registries.
200+
definitions:
201+
- selector:
202+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
203+
kind: DockerMachineTemplate
204+
matchResources:
205+
controlPlane: true
206+
machineDeploymentClass:
207+
names:
208+
- default-worker
209+
jsonPatches:
210+
- op: add
211+
path: "/spec/template/spec/preLoadImages"
212+
valueFrom:
213+
variable: preLoadImages
186214
- name: kubeadmControlPlaneMaxSurge
187215
description: "Sets the maxSurge value used for rolloutStrategy in the KubeadmControlPlane."
188216
enabledIf: '{{ ne .kubeadmControlPlaneMaxSurge "" }}'

test/e2e/self_hosted.go

Lines changed: 173 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,22 @@ package e2e
1818

1919
import (
2020
"context"
21-
"errors"
2221
"fmt"
2322
"os"
2423
"path/filepath"
24+
"strings"
2525

2626
. "github.com/onsi/ginkgo/v2"
2727
. "github.com/onsi/gomega"
2828
corev1 "k8s.io/api/core/v1"
2929
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
30-
"k8s.io/klog/v2"
3130
"k8s.io/utils/pointer"
3231
"sigs.k8s.io/controller-runtime/pkg/client"
3332

3433
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
34+
clusterctlv1 "sigs.k8s.io/cluster-api/cmd/clusterctl/api/v1alpha3"
3535
"sigs.k8s.io/cluster-api/test/e2e/internal/log"
3636
"sigs.k8s.io/cluster-api/test/framework"
37-
"sigs.k8s.io/cluster-api/test/framework/bootstrap"
3837
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
3938
"sigs.k8s.io/cluster-api/util"
4039
)
@@ -48,6 +47,22 @@ type SelfHostedSpecInput struct {
4847
SkipCleanup bool
4948
ControlPlaneWaiters clusterctl.ControlPlaneWaiters
5049
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
5166
}
5267

5368
// 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)
6479
selfHostedNamespace *corev1.Namespace
6580
selfHostedCancelWatches context.CancelFunc
6681
selfHostedCluster *clusterv1.Cluster
82+
83+
controlPlaneMachineCount int64
84+
workerMachineCount int64
85+
86+
kubernetesVersion string
6787
)
6888

6989
BeforeEach(func() {
@@ -73,17 +93,60 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
7393
Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName)
7494
Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
7595
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+
}
77110

78111
// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
79112
namespace, cancelWatches = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder)
80113
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+
}
81126
})
82127

83128
It("Should pivot the bootstrap cluster to a self-hosted cluster", func() {
84129
By("Creating a workload cluster")
85130

86131
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+
87150
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
88151
ClusterProxy: input.BootstrapClusterProxy,
89152
ConfigCluster: clusterctl.ConfigClusterInput{
@@ -94,9 +157,10 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
94157
Flavor: input.Flavor,
95158
Namespace: namespace.Name,
96159
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,
100164
},
101165
ControlPlaneWaiters: input.ControlPlaneWaiters,
102166
WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"),
@@ -106,34 +170,7 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
106170

107171
By("Turning the workload cluster into a management cluster")
108172

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.
115173
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-
137174
// Get a ClusterBroker so we can interact with the workload cluster
138175
selfHostedClusterProxy = input.BootstrapClusterProxy.GetWorkloadCluster(ctx, cluster.Namespace, cluster.Name)
139176

@@ -217,6 +254,94 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
217254
return matchUnstructuredLists(preMoveMachineList, postMoveMachineList)
218255
}, "3m", "30s").Should(BeTrue(), "Machines should not roll out after move to self-hosted cluster")
219256

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+
220345
By("PASSED!")
221346
})
222347

@@ -267,3 +392,17 @@ func SelfHostedSpec(ctx context.Context, inputGetter func() SelfHostedSpecInput)
267392
dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
268393
})
269394
}
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

Comments
 (0)