Skip to content

Commit 9ea981f

Browse files
authored
Merge pull request #45 from aws/e2e_2_clusters
E2e 2 clusters
2 parents 63bab26 + a47dec9 commit 9ea981f

File tree

7 files changed

+217
-10
lines changed

7 files changed

+217
-10
lines changed

test/e2e/README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,18 @@ This section describes how to run end-to-end (e2e) testing with CAPC.
2222

2323
The first step to running the e2e tests is setting up the required environment variables:
2424

25-
| Environment variable | Description | Default Value |
26-
| -------------------------------------------- | ---------------------------------------------------------------------------------| --------------------------- |
27-
| `CLOUDSTACK_ZONE_NAME` | The zone name | `zone1` |
28-
| `CLOUDSTACK_NETWORK_NAME` | The network name. If not exisiting an isolated network with the name is created. | `Shared1` |
29-
| `CLUSTER_ENDPOINT_IP` | The cluster endpoint IP | `192.168.1.38` |
30-
| `CLUSTER_ENDPOINT_PORT` | The cluster endpoint port | `6443` |
31-
| `CLOUDSTACK_CONTROL_PLANE_MACHINE_OFFERING` | The machine offering for the control plane VM instances | `Large Instance` |
32-
| `CLOUDSTACK_WORKER_MACHINE_OFFERING` | The machine offering for the worker node VM instances | `Medium Instance` |
33-
| `CLOUDSTACK_TEMPLATE_NAME` | The machine template for both control plane and worke node VM instances | `kube-v1.20.10/ubuntu-2004` |
34-
| `CLOUDSTACK_SSH_KEY_NAME` | The name of SSH key added to the VM instances | `CAPCKeyPair6` |
25+
| Environment variable | Description | Default Value |
26+
|---------------------------------------------|----------------------------------------------------------------------------------|-----------------------------|
27+
| `CLOUDSTACK_ZONE_NAME` | The zone name | `zone1` |
28+
| `CLOUDSTACK_NETWORK_NAME` | The network name. If not exisiting an isolated network with the name is created. | `Shared1` |
29+
| `CLUSTER_ENDPOINT_IP` | The cluster endpoint IP | `172.16.2.199` |
30+
| `CLUSTER_ENDPOINT_PORT` | The cluster endpoint port | `6443` |
31+
| `CLUSTER_ENDPOINT_IP_2` | The cluster endpoint IP for a second cluster | `172.16.2.199` |
32+
| `CLUSTER_ENDPOINT_PORT_2` | The cluster endpoint port for a second cluster | `6444` |
33+
| `CLOUDSTACK_CONTROL_PLANE_MACHINE_OFFERING` | The machine offering for the control plane VM instances | `Large Instance` |
34+
| `CLOUDSTACK_WORKER_MACHINE_OFFERING` | The machine offering for the worker node VM instances | `Medium Instance` |
35+
| `CLOUDSTACK_TEMPLATE_NAME` | The machine template for both control plane and worke node VM instances | `kube-v1.20.10/ubuntu-2004` |
36+
| `CLOUDSTACK_SSH_KEY_NAME` | The name of SSH key added to the VM instances | `CAPCKeyPair6` |
3537

3638
You also have to export `CLOUDSTACK_B64ENCODED_SECRET` environment variable using this command `export CLOUDSTACK_B64ENCODED_SECRET=$(base64 -i cloud-config)` after creating `cloud-config` file with the following format.
3739

test/e2e/common.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"encoding/base64"
2222
"errors"
2323
"fmt"
24+
apierrors "k8s.io/apimachinery/pkg/api/errors"
2425
"os"
2526
"path/filepath"
2627
"strings"
@@ -415,3 +416,26 @@ func HasMatchingUnhealthyConditions(machineHealthCheck *clusterv1.MachineHealthC
415416
}
416417
return false
417418
}
419+
420+
func ClusterExists(ctx context.Context, mgmtClient client.Client, cluster *clusterv1.Cluster) bool {
421+
key := client.ObjectKey{
422+
Namespace: cluster.GetNamespace(),
423+
Name: cluster.GetName(),
424+
}
425+
return !apierrors.IsNotFound(mgmtClient.Get(ctx, key, &clusterv1.Cluster{}))
426+
}
427+
428+
func IsClusterReady(ctx context.Context, mgmtClient client.Client, cluster *clusterv1.Cluster) bool {
429+
key := client.ObjectKey{
430+
Namespace: cluster.GetNamespace(),
431+
Name: cluster.GetName(),
432+
}
433+
c := &clusterv1.Cluster{}
434+
err := mgmtClient.Get(ctx, key, c)
435+
436+
if apierrors.IsNotFound(err) {
437+
return false
438+
}
439+
Expect(err).To(BeNil(), "Failed to get cluster status")
440+
return c.Status.ControlPlaneReady && c.Status.InfrastructureReady
441+
}

test/e2e/config/cloudstack.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ providers:
8686
- sourcePath: "../data/infrastructure-cloudstack/v1beta1/cluster-template-affinity-group-pro.yaml"
8787
- sourcePath: "../data/infrastructure-cloudstack/v1beta1/cluster-template-affinity-group-anti.yaml"
8888
- sourcePath: "../data/infrastructure-cloudstack/v1beta1/cluster-template-resource-cleanup.yaml"
89+
- sourcePath: "../data/infrastructure-cloudstack/v1beta1/cluster-template-second-cluster.yaml"
8990
- sourcePath: "../data/shared/v1beta1/metadata.yaml"
9091
versions:
9192
- name: v1.0.0
@@ -112,8 +113,10 @@ variables:
112113
CLOUDSTACK_NETWORK_NAME: isolated-for-e2e-1
113114
CLOUDSTACK_NEW_NETWORK_NAME: isolated-for-e2e-new
114115
CLUSTER_ENDPOINT_IP: 172.16.2.199
116+
CLUSTER_ENDPOINT_IP_2: 172.16.2.199
115117
CLUSTER_ENDPOINT_NEW_IP: 172.16.2.201
116118
CLUSTER_ENDPOINT_PORT: 6443
119+
CLUSTER_ENDPOINT_PORT_2: 6444
117120
CLOUDSTACK_CONTROL_PLANE_MACHINE_OFFERING: "Large Instance"
118121
CLOUDSTACK_INVALID_CONTROL_PLANE_MACHINE_OFFERING: "OfferingXXXX"
119122
CLOUDSTACK_EXTREMELY_LARGE_CONTROL_PLANE_MACHINE_OFFERING: "Extremely Large Instance"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: infrastructure.cluster.x-k8s.io/v1beta1
2+
kind: CloudStackCluster
3+
metadata:
4+
name: ${CLUSTER_NAME}
5+
spec:
6+
zones:
7+
- name: ${CLOUDSTACK_ZONE_NAME}
8+
network:
9+
name: ${CLOUDSTACK_NETWORK_NAME}
10+
account: ${CLOUDSTACK_ACCOUNT_NAME}
11+
domain: ${CLOUDSTACK_DOMAIN_NAME}
12+
controlPlaneEndpoint:
13+
host: ${CLUSTER_ENDPOINT_IP_2}
14+
port: ${CLUSTER_ENDPOINT_PORT_2}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bases:
2+
- ../bases/cluster-with-kcp.yaml
3+
- ../bases/md.yaml
4+
5+
patchesStrategicMerge:
6+
- ./cloudstack-cluster.yaml

test/e2e/two_clusters.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package e2e
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"os"
23+
"path/filepath"
24+
25+
. "github.com/onsi/ginkgo"
26+
. "github.com/onsi/gomega"
27+
corev1 "k8s.io/api/core/v1"
28+
"k8s.io/utils/pointer"
29+
30+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
31+
"sigs.k8s.io/cluster-api/util"
32+
)
33+
34+
// TwoClustersSpec implements a test that verifies two clusters can co-exist.
35+
func TwoClustersSpec(ctx context.Context, inputGetter func() CommonSpecInput) {
36+
37+
const specName = "two-clusters"
38+
39+
var (
40+
input CommonSpecInput
41+
namespace1 *corev1.Namespace
42+
namespace2 *corev1.Namespace
43+
cancelWatches1 context.CancelFunc
44+
cancelWatches2 context.CancelFunc
45+
clusterResources1 *clusterctl.ApplyClusterTemplateAndWaitResult
46+
clusterResources2 *clusterctl.ApplyClusterTemplateAndWaitResult
47+
)
48+
49+
createCluster := func(flavor string, namespace *corev1.Namespace, resources *clusterctl.ApplyClusterTemplateAndWaitResult) {
50+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
51+
ClusterProxy: input.BootstrapClusterProxy,
52+
CNIManifestPath: input.E2EConfig.GetVariable(CNIPath),
53+
ConfigCluster: clusterctl.ConfigClusterInput{
54+
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()),
55+
ClusterctlConfigPath: input.ClusterctlConfigPath,
56+
KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(),
57+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
58+
Flavor: flavor,
59+
Namespace: namespace.Name,
60+
ClusterName: fmt.Sprintf("%s-%s", specName, util.RandomString(6)),
61+
KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion),
62+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
63+
WorkerMachineCount: pointer.Int64Ptr(1),
64+
},
65+
WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"),
66+
WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"),
67+
WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"),
68+
}, resources)
69+
}
70+
71+
BeforeEach(func() {
72+
Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName)
73+
input = inputGetter()
74+
Expect(input.E2EConfig).ToNot(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName)
75+
Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName)
76+
Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
77+
Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName)
78+
Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion))
79+
Expect(input.E2EConfig.Variables).To(HaveValidVersion(input.E2EConfig.GetVariable(KubernetesVersion)))
80+
81+
// Set up namespaces to host objects for this spec and create watchers for the namespace events.
82+
namespace1, cancelWatches1 = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder)
83+
namespace2, cancelWatches2 = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder)
84+
clusterResources1 = new(clusterctl.ApplyClusterTemplateAndWaitResult)
85+
clusterResources2 = new(clusterctl.ApplyClusterTemplateAndWaitResult)
86+
})
87+
88+
It("should successfully add and remove a second cluster without breaking the first cluster", func() {
89+
mgmtClient := input.BootstrapClusterProxy.GetClient()
90+
91+
By("Create the first cluster and verify that it's ready")
92+
createCluster(clusterctl.DefaultFlavor, namespace1, clusterResources1)
93+
Expect(IsClusterReady(ctx, mgmtClient, clusterResources1.Cluster)).To(BeTrue())
94+
95+
By("Create the second cluster and verify that it's ready")
96+
createCluster("second-cluster", namespace2, clusterResources2)
97+
Expect(IsClusterReady(ctx, mgmtClient, clusterResources2.Cluster)).To(BeTrue())
98+
99+
By("Delete the second cluster")
100+
dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace2,
101+
cancelWatches2, clusterResources2.Cluster, input.E2EConfig.GetIntervals, false)
102+
103+
By("Verify the second cluster is gone")
104+
Expect(ClusterExists(ctx, mgmtClient, clusterResources2.Cluster)).To(BeFalse())
105+
106+
By("Verify the first cluster is still ready")
107+
Expect(IsClusterReady(ctx, mgmtClient, clusterResources1.Cluster)).To(BeTrue())
108+
109+
By("PASSED!")
110+
})
111+
112+
AfterEach(func() {
113+
// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
114+
dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace1, cancelWatches1, clusterResources1.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
115+
if clusterResources2.Cluster != nil && ClusterExists(ctx, input.BootstrapClusterProxy.GetClient(), clusterResources2.Cluster) {
116+
dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace2, cancelWatches2, clusterResources2.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
117+
}
118+
})
119+
}

test/e2e/two_clusters_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
/*
5+
Copyright 2021 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+
. "github.com/onsi/ginkgo"
25+
)
26+
27+
var _ = Describe("with two clusters", func() {
28+
29+
TwoClustersSpec(context.TODO(), func() CommonSpecInput {
30+
return CommonSpecInput{
31+
E2EConfig: e2eConfig,
32+
ClusterctlConfigPath: clusterctlConfigPath,
33+
BootstrapClusterProxy: bootstrapClusterProxy,
34+
ArtifactFolder: artifactFolder,
35+
SkipCleanup: skipCleanup,
36+
}
37+
})
38+
39+
})

0 commit comments

Comments
 (0)