Skip to content

Commit 8468d84

Browse files
Merge pull request #193 from jweite-amazon/e2e_network_interruption
First draft of Toxiproxy-based network interruption E2E test.
2 parents 27e0212 + 77102af commit 8468d84

12 files changed

+275
-35
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ E2E_CONFIG ?= ${REPO_ROOT}/test/e2e/config/cloudstack.yaml
284284
run-e2e: e2e-essentials ## Run e2e testing. JOB is an optional REGEXP to select certainn test cases to run. e.g. JOB=PR-Blocking, JOB=Conformance
285285
$(KUBECTL) apply -f cloud-config.yaml && \
286286
cd test/e2e && \
287-
$(GINKGO_V1) -v -trace -tags=e2e -focus=$(JOB) -skip=Conformance -skipPackage=helpers -nodes=1 -noColor=false ./... -- \
287+
$(GINKGO_V1) -v -trace -tags=e2e -focus=$(JOB) -skip=Conformance -skipPackage=kubeconfig_helper -nodes=1 -noColor=false ./... -- \
288288
-e2e.artifacts-folder=${REPO_ROOT}/_artifacts \
289289
-e2e.config=${E2E_CONFIG} \
290290
-e2e.skip-resource-cleanup=false -e2e.use-existing-cluster=true

test/e2e/deploy_app_toxi.go

+7-7
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"os"
2727
"path/filepath"
2828
"runtime"
29-
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
29+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/toxiproxy"
3030
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
3131
"sigs.k8s.io/cluster-api/util"
3232
)
@@ -41,8 +41,8 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
4141
clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
4242
bootstrapClusterToxicName string
4343
cloudStackToxicName string
44-
bootstrapClusterToxiProxyContext *helpers.ToxiProxyContext
45-
cloudStackToxiProxyContext *helpers.ToxiProxyContext
44+
bootstrapClusterToxiProxyContext *toxiproxy.Context
45+
cloudStackToxiProxyContext *toxiproxy.Context
4646
appName = "httpd"
4747
appManifestPath = "data/fixture/sample-application.yaml"
4848
expectedHtmlPath = "data/fixture/expected-webpage.html"
@@ -66,14 +66,14 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
6666
Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion))
6767

6868
// Set up a toxiProxy for bootstrap server.
69-
bootstrapClusterToxiProxyContext = helpers.SetupForToxiProxyTestingBootstrapCluster(input.BootstrapClusterProxy, clusterName)
69+
bootstrapClusterToxiProxyContext = toxiproxy.SetupForToxiProxyTestingBootstrapCluster(input.BootstrapClusterProxy, clusterName)
7070
const ToxicLatencyMs = 100
7171
const ToxicJitterMs = 100
7272
const ToxicToxicity = 1
7373
bootstrapClusterToxicName = bootstrapClusterToxiProxyContext.AddLatencyToxic(ToxicLatencyMs, ToxicJitterMs, ToxicToxicity, false)
7474

7575
// Set up a toxiProxy for CloudStack
76-
cloudStackToxiProxyContext = helpers.SetupForToxiProxyTestingACS(
76+
cloudStackToxiProxyContext = toxiproxy.SetupForToxiProxyTestingACS(
7777
ctx,
7878
clusterName,
7979
input.BootstrapClusterProxy,
@@ -154,9 +154,9 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
154154

155155
// Tear down the ToxiProxies
156156
cloudStackToxiProxyContext.RemoveToxic(cloudStackToxicName)
157-
helpers.TearDownToxiProxyACS(ctx, input.BootstrapClusterProxy, cloudStackToxiProxyContext)
157+
toxiproxy.TearDownToxiProxyACS(ctx, input.BootstrapClusterProxy, cloudStackToxiProxyContext)
158158

159159
bootstrapClusterToxiProxyContext.RemoveToxic(bootstrapClusterToxicName)
160-
helpers.TearDownToxiProxyBootstrap(bootstrapClusterToxiProxyContext)
160+
toxiproxy.TearDownToxiProxyBootstrap(bootstrapClusterToxiProxyContext)
161161
})
162162
}

test/e2e/e2e_suite_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import (
2525
"os"
2626
"path/filepath"
2727
go_runtime "runtime"
28-
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
28+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/toxiproxy"
2929
"strings"
3030
"testing"
3131
"time"
@@ -122,7 +122,7 @@ var _ = SynchronizedBeforeSuite(func() []byte {
122122
// Toxiproxy running in a docker container requires docker host networking, only available in linux.
123123
if go_runtime.GOOS == "linux" {
124124
By("Launching Toxiproxy Server")
125-
Expect(helpers.ToxiProxyServerExec(ctx)).To(Succeed())
125+
Expect(toxiproxy.ServerExec(ctx)).To(Succeed())
126126
}
127127

128128
if clusterctlConfig == "" {
@@ -180,7 +180,7 @@ var _ = SynchronizedAfterSuite(func() {
180180

181181
if go_runtime.GOOS == "linux" {
182182
By("Killing Toxiproxy Server")
183-
Expect(helpers.ToxiProxyServerKill(ctx)).To(Succeed())
183+
Expect(toxiproxy.ServerKill(ctx)).To(Succeed())
184184
}
185185
})
186186

test/e2e/helpers/utilities.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
Copyright 2022 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 helpers
18+
19+
import "time"
20+
21+
func InterruptibleSleep(sleepDuration time.Duration, resolution time.Duration, interruptionChannel chan bool) bool {
22+
for entryTime := time.Now(); time.Since(entryTime) < sleepDuration; {
23+
select {
24+
case <-interruptionChannel:
25+
return true
26+
default:
27+
time.Sleep(resolution)
28+
break
29+
}
30+
}
31+
return false
32+
}
File renamed without changes.
File renamed without changes.

test/e2e/helpers/kubeconfig.go test/e2e/kubeconfig_helper/kubeconfig.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package helpers
17+
package kubeconfig_helper
1818

1919
import (
2020
"errors"

test/e2e/helpers/kubeconfig_test.go test/e2e/kubeconfig_helper/kubeconfig_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,18 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package helpers_test
17+
package kubeconfig_helper_test
1818

1919
import (
2020
. "github.com/onsi/ginkgo"
2121
. "github.com/onsi/gomega"
2222
"io/ioutil"
23-
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
23+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/kubeconfig_helper"
2424
)
2525

2626
var _ = Describe("Test kubeconfig helper methods", func() {
2727
It("should work", func() {
28-
kubeconfig := helpers.NewKubeconfig()
28+
kubeconfig := kubeconfig_helper.NewKubeconfig()
2929

3030
var kubeconfigPath string = "./data/kubeconfig"
3131
var unmodifiedKubeconfigPath string = "/tmp/unmodifiedKubeconfig"

test/e2e/helpers/suite_test.go test/e2e/kubeconfig_helper/suite_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package helpers_test
1+
package kubeconfig_helper_test
22

33
import (
44
"testing"
@@ -9,5 +9,5 @@ import (
99

1010
func TestHelpers(t *testing.T) {
1111
RegisterFailHandler(Fail)
12-
RunSpecs(t, "Helpers Suite")
12+
RunSpecs(t, "Kubeconfig Helpers Suite")
1313
}

test/e2e/network_interruption_toxi.go

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
Copyright 2022 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+
"runtime"
25+
"time"
26+
27+
. "github.com/onsi/ginkgo"
28+
. "github.com/onsi/gomega"
29+
corev1 "k8s.io/api/core/v1"
30+
"k8s.io/utils/pointer"
31+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
32+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/toxiproxy"
33+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
34+
"sigs.k8s.io/cluster-api/util"
35+
)
36+
37+
// NetworkInterruptionToxiSpec implements a test that verifies that an app deployed to the workload cluster works.
38+
func NetworkInterruptionToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput) {
39+
var (
40+
specName = "network-interruption-toxi"
41+
input CommonSpecInput
42+
namespace *corev1.Namespace
43+
cancelWatches context.CancelFunc
44+
clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
45+
cloudStackToxiProxyContext *toxiproxy.Context
46+
clusterName = fmt.Sprintf("%s-%s", specName, util.RandomString(6))
47+
networkInterruptorShutdownChannel = make(chan bool, 2)
48+
)
49+
50+
BeforeEach(func() {
51+
// ToxiProxy running in a docker container requires docker host networking, only available in linux.
52+
Expect(runtime.GOOS).To(Equal("linux"))
53+
54+
Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName)
55+
input = inputGetter()
56+
Expect(input.E2EConfig).ToNot(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName)
57+
Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName)
58+
Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
59+
Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName)
60+
Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion))
61+
62+
// Set up a toxiProxy for CloudStack
63+
cloudStackToxiProxyContext = toxiproxy.SetupForToxiProxyTestingACS(
64+
ctx,
65+
clusterName,
66+
input.BootstrapClusterProxy,
67+
input.E2EConfig,
68+
input.ClusterctlConfigPath,
69+
)
70+
71+
// Set up a Namespace to host objects for this spec and create a watcher for the namespace events.
72+
namespace, cancelWatches = setupSpecNamespace(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder)
73+
clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult)
74+
75+
})
76+
77+
It("Should be able to create a cluster despite a network interruption during that process", func() {
78+
By("Creating a workload cluster")
79+
80+
flavor := clusterctl.DefaultFlavor
81+
if input.Flavor != nil {
82+
flavor = *input.Flavor
83+
}
84+
namespace := namespace.Name
85+
86+
// While I'd prefer to closely synchronize the network interruption (ToxiProxy disable) to a particular point in the cluster provisioning
87+
// process, doing so for this asynchronously running process would be harder and more impactful than I can tackle right now. So I'm going
88+
// to give CAPC a short period to get started with the provisioning, and then interrupt the network for a fixed time, and then restore it.
89+
// CAPC should tolerate this and ultimately succeed.
90+
// To do this while ApplyClusterTemplateAndWait() is waiting, I'm going to use a concurrent goroutine and an interruptible version of sleep
91+
// so it can be shut down cleanly.
92+
go networkInterruptor(cloudStackToxiProxyContext, networkInterruptorShutdownChannel)
93+
94+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
95+
ClusterProxy: input.BootstrapClusterProxy,
96+
CNIManifestPath: input.E2EConfig.GetVariable(CNIPath),
97+
ConfigCluster: clusterctl.ConfigClusterInput{
98+
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", input.BootstrapClusterProxy.GetName()),
99+
ClusterctlConfigPath: cloudStackToxiProxyContext.ConfigPath,
100+
KubeconfigPath: input.BootstrapClusterProxy.GetKubeconfigPath(),
101+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
102+
Flavor: flavor,
103+
Namespace: namespace,
104+
ClusterName: clusterName,
105+
KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion),
106+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
107+
WorkerMachineCount: pointer.Int64Ptr(2),
108+
},
109+
WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"),
110+
WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"),
111+
WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"),
112+
}, clusterResources)
113+
114+
By("PASSED!")
115+
})
116+
117+
AfterEach(func() {
118+
// Stop the networkInterruptor (in case it's still running because tests failed before it completed)
119+
networkInterruptorShutdownChannel <- true
120+
121+
// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
122+
dumpSpecResourcesAndCleanup(ctx, specName, input.BootstrapClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
123+
124+
// Tear down the ToxiProxies
125+
toxiproxy.TearDownToxiProxyACS(ctx, input.BootstrapClusterProxy, cloudStackToxiProxyContext)
126+
})
127+
}
128+
129+
func networkInterruptor(toxiProxyContext *toxiproxy.Context, shutdownChannel chan bool) {
130+
for {
131+
// Wait for ApplyClusterTemplateAndWait() to make some progress
132+
helpers.InterruptibleSleep(15*time.Second, time.Second, shutdownChannel)
133+
134+
// Disable communications to ACS
135+
toxiProxyContext.Disable()
136+
137+
// Leave the network disabled for some period of time
138+
helpers.InterruptibleSleep(30*time.Second, time.Second, shutdownChannel)
139+
140+
// Restore communications to ACS
141+
toxiProxyContext.Enable()
142+
}
143+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//go:build e2e
2+
// +build e2e
3+
4+
/*
5+
Copyright 2020 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("When testing app deployment to the workload cluster with network interruption [ToxiProxy]", func() {
28+
29+
NetworkInterruptionToxiSpec(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)