Skip to content

Commit ca0c351

Browse files
authored
Adding e2e test cases (#506)
1 parent 48790f4 commit ca0c351

File tree

5 files changed

+329
-9
lines changed

5 files changed

+329
-9
lines changed

Makefile

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ ARTIFACTS ?= $(REPO_ROOT)/_artifacts
2727
TOOLS_DIR := hack/tools
2828
TOOLS_BIN_DIR := $(TOOLS_DIR)/bin
2929
GO_INSTALL = ./scripts/go_install.sh
30-
E2E_CONF_FILE ?= $(REPO_ROOT)/test/e2e/config/ibmcloud-e2e.yaml
3130
E2E_CONF_FILE_ENVSUBST := $(REPO_ROOT)/test/e2e/config/ibmcloud-e2e-envsubst.yaml
3231

3332
GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint
@@ -135,6 +134,14 @@ generate-go: $(MOCKGEN)
135134

136135
images: docker-build
137136

137+
set-flavor:
138+
ifeq ($(E2E_FLAVOR), vpc)
139+
$(eval E2E_CONF_FILE=$(REPO_ROOT)/test/e2e/config/ibmcloud-e2e-vpc.yaml)
140+
else
141+
$(eval E2E_CONF_FILE=$(REPO_ROOT)/test/e2e/config/ibmcloud-e2e-powervs.yaml)
142+
endif
143+
@echo "Setting e2e test flavour to ${E2E_CONF_FILE}"
144+
138145
## --------------------------------------
139146
## Linting
140147
## --------------------------------------
@@ -155,20 +162,22 @@ test: generate fmt vet manifests
155162
GINKGO_FOCUS ?= Workload cluster creation
156163
GINKGO_NODES ?= 3
157164
GINKGO_NOCOLOR ?= false
165+
E2E_FLAVOR ?= powervs
158166
GINKGO_ARGS ?= -v -trace -progress -v -tags=e2e -focus=$(GINKGO_FOCUS) -nodes=$(GINKGO_NODES) --noColor=$(GINKGO_NOCOLOR)
159167
ARTIFACTS ?= $(REPO_ROOT)/_artifacts
160168
SKIP_CLEANUP ?= false
161169
SKIP_CREATE_MGMT_CLUSTER ?= false
162170

163171
#Run the end-to-end tests
164172
.PHONY: test-e2e
165-
test-e2e: $(KUBECTL) $(GINKGO) $(ENVSUBST) e2e-image
173+
test-e2e: $(KUBECTL) $(GINKGO) $(ENVSUBST) set-flavor e2e-image
166174
$(ENVSUBST) < $(E2E_CONF_FILE) > $(E2E_CONF_FILE_ENVSUBST)
167175
$(GINKGO) $(GINKGO_ARGS) ./test/e2e -- \
168-
-e2e.artifacts-folder="$(ARTIFACTS)" \
169-
-e2e.config="$(E2E_CONF_FILE_ENVSUBST)" \
170-
-e2e.skip-resource-cleanup=$(SKIP_CLEANUP) \
171-
-e2e.use-existing-cluster=$(SKIP_CREATE_MGMT_CLUSTER)
176+
-e2e.artifacts-folder="$(ARTIFACTS)" \
177+
-e2e.config="$(E2E_CONF_FILE_ENVSUBST)" \
178+
-e2e.skip-resource-cleanup=$(SKIP_CLEANUP) \
179+
-e2e.use-existing-cluster=$(SKIP_CREATE_MGMT_CLUSTER) \
180+
-e2e.flavor="$(E2E_FLAVOR)"
172181

173182
## --------------------------------------
174183
## Docker
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
managementClusterName: capi-ibmcloud-e2e
2+
3+
images:
4+
# Use local built images for e2e tests
5+
- name: gcr.io/k8s-staging-capi-ibmcloud/cluster-api-ibmcloud-controller:e2e
6+
loadBehavior: mustLoad
7+
8+
providers:
9+
- name: cluster-api
10+
type: CoreProvider
11+
versions:
12+
- name: v1.0.2
13+
value: https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.2/core-components.yaml
14+
type: url
15+
files:
16+
- sourcePath: "${PWD}/test/e2e/data/shared/metadata.yaml"
17+
- name: kubeadm
18+
type: BootstrapProvider
19+
versions:
20+
- name: v1.0.2
21+
value: https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.2/bootstrap-components.yaml
22+
type: url
23+
files:
24+
- sourcePath: "${PWD}/test/e2e/data/shared/metadata.yaml"
25+
- name: kubeadm
26+
type: ControlPlaneProvider
27+
versions:
28+
- name: v1.0.2
29+
value: https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.0.2/control-plane-components.yaml
30+
type: url
31+
files:
32+
- sourcePath: "${PWD}/test/e2e/data/shared/metadata.yaml"
33+
- name: ibmcloud
34+
type: InfrastructureProvider
35+
versions:
36+
- name: v0.2.0
37+
value: "${PWD}/config/default"
38+
files:
39+
- sourcePath: "${PWD}/metadata.yaml"
40+
targetName: "metadata.yaml"
41+
- sourcePath: "${PWD}/templates/cluster-template-powervs.yaml"
42+
targetName: "cluster-template-powervs.yaml"
43+
44+
variables:
45+
KUBERNETES_VERSION: "${KUBERNETES_VERSION:-v1.22.4}"
46+
# Cluster Addons
47+
CNI: "${PWD}/test/e2e/data/cni/calico/calico.yaml"
48+
IP_FAMILY: "IPv4"
49+
# Following variables should be set based on the flavour being tested
50+
IBMPOWERVS_SSHKEY_NAME: "${IBMPOWERVS_SSHKEY_NAME:-}"
51+
IBMPOWERVS_VIP: "${IBMPOWERVS_VIP:-}"
52+
IBMPOWERVS_VIP_EXTERNAL: "${IBMPOWERVS_VIP_EXTERNAL:-}"
53+
IBMPOWERVS_VIP_CIDR: "${IBMPOWERVS_VIP_CIDR:-}"
54+
IBMPOWERVS_IMAGE_NAME: "${IBMPOWERVS_IMAGE_NAME:-}"
55+
IBMPOWERVS_SERVICE_INSTANCE_ID: "${IBMPOWERVS_SERVICE_INSTANCE_ID:-}"
56+
IBMPOWERVS_NETWORK_NAME: "${IBMPOWERVS_NETWORK_NAME:-}"
57+
58+
intervals:
59+
default/wait-controllers: ["3m", "10s"]
60+
default/wait-cluster: ["20m", "10s"]
61+
default/wait-control-plane: ["30m", "10s"]
62+
default/wait-worker-nodes: ["30m", "10s"]
63+
default/wait-delete-cluster: ["20m", "10s"]
64+
default/wait-machine-upgrade: ["50m", "10s"]
65+
default/wait-machine-remediation: ["30m", "10s"]
66+
default/wait-deployment: ["5m", "10s"]
67+
default/wait-job: ["5m", "10s"]
68+
default/wait-service: ["3m", "10s"]

test/e2e/config/ibmcloud-e2e.yaml renamed to test/e2e/config/ibmcloud-e2e-vpc.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,6 @@ providers:
3838
files:
3939
- sourcePath: "${PWD}/metadata.yaml"
4040
targetName: "metadata.yaml"
41-
- sourcePath: "${PWD}/templates/cluster-template-powervs.yaml"
42-
targetName: "cluster-template-powervs.yaml"
4341
- sourcePath: "${PWD}/templates/cluster-template.yaml"
4442
targetName: "cluster-template-vpc.yaml"
4543

@@ -48,7 +46,15 @@ variables:
4846
# Cluster Addons
4947
CNI: "${PWD}/test/e2e/data/cni/calico/calico.yaml"
5048
IP_FAMILY: "IPv4"
51-
49+
# Following variables should be set based on the flavour being tested
50+
IBMVPC_REGION: "${IBMVPC_REGION:-}"
51+
IBMVPC_ZONE: "${IBMVPC_ZONE:-}"
52+
IBMVPC_RESOURCEGROUP: "${IBMVPC_RESOURCEGROUP:-}"
53+
IBMVPC_NAME: "${IBMVPC_NAME:-}"
54+
IBMVPC_IMAGE_ID: "${IBMVPC_IMAGE_ID:-}"
55+
IBMVPC_PROFILE: "${IBMVPC_PROFILE:-}"
56+
IBMVPC_SSHKEY_ID: "${IBMVPC_SSHKEY_ID:-}"
57+
5258
intervals:
5359
default/wait-controllers: ["3m", "10s"]
5460
default/wait-cluster: ["20m", "10s"]

test/e2e/e2e_test.go

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
/*
5+
Copyright 2022 The Kubernetes Authors.
6+
7+
Licensed under the Apache License, Version 2.0 (the "License");
8+
you may not use this file except in compliance with the License.
9+
You may obtain a copy of the License at
10+
11+
http://www.apache.org/licenses/LICENSE-2.0
12+
13+
Unless required by applicable law or agreed to in writing, software
14+
distributed under the License is distributed on an "AS IS" BASIS,
15+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
See the License for the specific language governing permissions and
17+
limitations under the License.
18+
*/
19+
20+
package e2e
21+
22+
import (
23+
"context"
24+
"fmt"
25+
"os"
26+
"path/filepath"
27+
28+
. "github.com/onsi/ginkgo"
29+
. "github.com/onsi/gomega"
30+
31+
corev1 "k8s.io/api/core/v1"
32+
"k8s.io/utils/pointer"
33+
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
34+
35+
capi_e2e "sigs.k8s.io/cluster-api/test/e2e"
36+
"sigs.k8s.io/cluster-api/test/framework"
37+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
38+
"sigs.k8s.io/cluster-api/util"
39+
)
40+
41+
var _ = Describe("Workload cluster creation", func() {
42+
var (
43+
ctx = context.TODO()
44+
specName = "create-workload-cluster"
45+
namespace *corev1.Namespace
46+
cancelWatches context.CancelFunc
47+
result *clusterctl.ApplyClusterTemplateAndWaitResult
48+
clusterName string
49+
clusterctlLogFolder string
50+
cniPath string
51+
)
52+
53+
BeforeEach(func() {
54+
Expect(e2eConfig).ToNot(BeNil(), "Invalid argument. e2eConfig can't be nil when calling %s spec", specName)
55+
Expect(clusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. clusterctlConfigPath must be an existing file when calling %s spec", specName)
56+
Expect(bootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. bootstrapClusterProxy can't be nil when calling %s spec", specName)
57+
Expect(os.MkdirAll(artifactFolder, 0755)).To(Succeed(), "Invalid argument. artifactFolder can't be created for %s spec", specName)
58+
59+
Expect(e2eConfig.Variables).To(HaveKey(KubernetesVersion))
60+
61+
clusterName = fmt.Sprintf("capi-ibmcloud-e2e-%s", util.RandomString(6))
62+
63+
// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
64+
namespace, cancelWatches = setupSpecNamespace(ctx, specName, bootstrapClusterProxy, artifactFolder)
65+
66+
result = new(clusterctl.ApplyClusterTemplateAndWaitResult)
67+
68+
// We need to override clusterctl apply log folder to avoid getting our credentials exposed.
69+
clusterctlLogFolder = filepath.Join(os.TempDir(), "clusters", bootstrapClusterProxy.GetName())
70+
71+
// Path to the CNI file is defined in the config
72+
Expect(e2eConfig.Variables).To(HaveKey(capi_e2e.CNIPath), "Missing %s variable in the config", capi_e2e.CNIPath)
73+
cniPath = e2eConfig.GetVariable(capi_e2e.CNIPath)
74+
})
75+
76+
AfterEach(func() {
77+
cleanInput := cleanupInput{
78+
SpecName: specName,
79+
Cluster: result.Cluster,
80+
ClusterProxy: bootstrapClusterProxy,
81+
Namespace: namespace,
82+
CancelWatches: cancelWatches,
83+
IntervalsGetter: e2eConfig.GetIntervals,
84+
SkipCleanup: skipCleanup,
85+
ArtifactFolder: artifactFolder,
86+
}
87+
88+
dumpSpecResourcesAndCleanup(ctx, cleanInput)
89+
})
90+
91+
Context("Creating a single control-plane cluster", func() {
92+
It("Should create a cluster with 1 worker node and can be scaled", func() {
93+
By("Initializing with 1 worker node")
94+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
95+
ClusterProxy: bootstrapClusterProxy,
96+
ConfigCluster: clusterctl.ConfigClusterInput{
97+
LogFolder: clusterctlLogFolder,
98+
ClusterctlConfigPath: clusterctlConfigPath,
99+
KubeconfigPath: bootstrapClusterProxy.GetKubeconfigPath(),
100+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
101+
Flavor: flavor,
102+
Namespace: namespace.Name,
103+
ClusterName: clusterName,
104+
KubernetesVersion: e2eConfig.GetVariable(KubernetesVersion),
105+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
106+
WorkerMachineCount: pointer.Int64Ptr(1),
107+
},
108+
CNIManifestPath: cniPath,
109+
WaitForClusterIntervals: e2eConfig.GetIntervals(specName, "wait-cluster"),
110+
WaitForControlPlaneIntervals: e2eConfig.GetIntervals(specName, "wait-control-plane"),
111+
WaitForMachineDeployments: e2eConfig.GetIntervals(specName, "wait-worker-nodes"),
112+
}, result)
113+
114+
By("Scaling worker node to 3")
115+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
116+
ClusterProxy: bootstrapClusterProxy,
117+
ConfigCluster: clusterctl.ConfigClusterInput{
118+
LogFolder: clusterctlLogFolder,
119+
ClusterctlConfigPath: clusterctlConfigPath,
120+
KubeconfigPath: bootstrapClusterProxy.GetKubeconfigPath(),
121+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
122+
Flavor: flavor,
123+
Namespace: namespace.Name,
124+
ClusterName: clusterName,
125+
KubernetesVersion: e2eConfig.GetVariable(KubernetesVersion),
126+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
127+
WorkerMachineCount: pointer.Int64Ptr(3),
128+
},
129+
CNIManifestPath: cniPath,
130+
WaitForClusterIntervals: e2eConfig.GetIntervals(specName, "wait-cluster"),
131+
WaitForControlPlaneIntervals: e2eConfig.GetIntervals(specName, "wait-control-plane"),
132+
WaitForMachineDeployments: e2eConfig.GetIntervals(specName, "wait-worker-nodes"),
133+
}, result)
134+
})
135+
})
136+
137+
Context("Creating a highly available control-plane cluster", func() {
138+
It("Should create a cluster with 3 control-plane nodes and 1 worker node", func() {
139+
By("Creating a high available cluster")
140+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
141+
ClusterProxy: bootstrapClusterProxy,
142+
ConfigCluster: clusterctl.ConfigClusterInput{
143+
LogFolder: clusterctlLogFolder,
144+
ClusterctlConfigPath: clusterctlConfigPath,
145+
KubeconfigPath: bootstrapClusterProxy.GetKubeconfigPath(),
146+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
147+
Flavor: flavor,
148+
Namespace: namespace.Name,
149+
ClusterName: clusterName,
150+
KubernetesVersion: e2eConfig.GetVariable(KubernetesVersion),
151+
ControlPlaneMachineCount: pointer.Int64Ptr(3),
152+
WorkerMachineCount: pointer.Int64Ptr(1),
153+
},
154+
CNIManifestPath: cniPath,
155+
WaitForClusterIntervals: e2eConfig.GetIntervals(specName, "wait-cluster"),
156+
WaitForControlPlaneIntervals: e2eConfig.GetIntervals(specName, "wait-control-plane"),
157+
WaitForMachineDeployments: e2eConfig.GetIntervals(specName, "wait-worker-nodes"),
158+
}, result)
159+
})
160+
})
161+
})
162+
163+
func Byf(format string, a ...interface{}) {
164+
By(fmt.Sprintf(format, a...))
165+
}
166+
167+
type cleanupInput struct {
168+
SpecName string
169+
ClusterProxy framework.ClusterProxy
170+
ArtifactFolder string
171+
Namespace *corev1.Namespace
172+
CancelWatches context.CancelFunc
173+
Cluster *clusterv1.Cluster
174+
IntervalsGetter func(spec, key string) []interface{}
175+
SkipCleanup bool
176+
AdditionalCleanup func()
177+
}
178+
179+
func setupSpecNamespace(ctx context.Context, specName string, clusterProxy framework.ClusterProxy, artifactFolder string) (*corev1.Namespace, context.CancelFunc) {
180+
Byf("Creating a namespace for hosting the %q test spec", specName)
181+
namespace, cancelWatches := framework.CreateNamespaceAndWatchEvents(ctx, framework.CreateNamespaceAndWatchEventsInput{
182+
Creator: clusterProxy.GetClient(),
183+
ClientSet: clusterProxy.GetClientSet(),
184+
Name: fmt.Sprintf("%s-%s", specName, util.RandomString(6)),
185+
LogFolder: filepath.Join(artifactFolder, "clusters", clusterProxy.GetName()),
186+
})
187+
188+
return namespace, cancelWatches
189+
}
190+
191+
func dumpSpecResourcesAndCleanup(ctx context.Context, input cleanupInput) {
192+
defer func() {
193+
input.CancelWatches()
194+
}()
195+
196+
if input.Cluster == nil {
197+
By("Unable to dump workload cluster logs as the cluster is nil")
198+
} else {
199+
Byf("Dumping logs from the %q workload cluster", input.Cluster.Name)
200+
input.ClusterProxy.CollectWorkloadClusterLogs(ctx, input.Cluster.Namespace, input.Cluster.Name, filepath.Join(input.ArtifactFolder, "clusters", input.Cluster.Name))
201+
}
202+
203+
Byf("Dumping all the Cluster API resources in the %q namespace", input.Namespace.Name)
204+
// Dump all Cluster API related resources to artifacts before deleting them.
205+
framework.DumpAllResources(ctx, framework.DumpAllResourcesInput{
206+
Lister: input.ClusterProxy.GetClient(),
207+
Namespace: input.Namespace.Name,
208+
LogPath: filepath.Join(input.ArtifactFolder, "clusters", input.ClusterProxy.GetName(), "resources"),
209+
})
210+
211+
if input.SkipCleanup {
212+
return
213+
}
214+
215+
Byf("Deleting all clusters in the %s namespace", input.Namespace.Name)
216+
framework.DeleteAllClustersAndWait(ctx, framework.DeleteAllClustersAndWaitInput{
217+
Client: input.ClusterProxy.GetClient(),
218+
Namespace: input.Namespace.Name,
219+
}, input.IntervalsGetter(input.SpecName, "wait-delete-cluster")...)
220+
221+
Byf("Deleting namespace used for hosting the %q test spec", input.SpecName)
222+
framework.DeleteNamespace(ctx, framework.DeleteNamespaceInput{
223+
Deleter: input.ClusterProxy.GetClient(),
224+
Name: input.Namespace.Name,
225+
})
226+
227+
if input.AdditionalCleanup != nil {
228+
Byf("Running additional cleanup for the %q test spec", input.SpecName)
229+
input.AdditionalCleanup()
230+
}
231+
}

0 commit comments

Comments
 (0)