Skip to content

Commit 691a791

Browse files
authoredOct 10, 2022
Merge pull request #165 from jweite-amazon/e2e-toxiproxy
E2e toxiproxy
2 parents d6f485f + d66275e commit 691a791

26 files changed

+910
-38
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ test/e2e/data/infrastructure-cloudstack/v1beta*/*yaml
4040

4141
# ACS Credentials file.
4242
cloud-config
43+
cloud-config.yaml
4344

4445
# Editor and IDE paraphernalia
4546
.idea

‎Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ tilt-up: cluster-api kind-cluster cluster-api/tilt-settings.json manifests ## Se
236236

237237
.PHONY: kind-cluster
238238
kind-cluster: cluster-api ## Create a kind cluster with a local Docker repository.
239-
-./cluster-api/hack/kind-install-for-capd.sh
239+
./cluster-api/hack/kind-install-for-capd.sh
240240

241241
cluster-api: ## Clone cluster-api repository for tilt use.
242242
git clone --branch v1.0.0 --depth 1 https://github.com/kubernetes-sigs/cluster-api.git
@@ -271,7 +271,7 @@ JOB ?= .*
271271
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
272272
$(KUBECTL) apply -f cloud-config.yaml && \
273273
cd test/e2e && \
274-
$(REPO_ROOT)/$(GINKGO_V1) -v -trace -tags=e2e -focus=$(JOB) -skip=Conformance -nodes=1 -noColor=false ./... -- \
274+
$(REPO_ROOT)/$(GINKGO_V1) -v -trace -tags=e2e -focus=$(JOB) -skip=Conformance -skipPackage=helpers -nodes=1 -noColor=false ./... -- \
275275
-e2e.artifacts-folder=${REPO_ROOT}/_artifacts \
276276
-e2e.config=${REPO_ROOT}/test/e2e/config/cloudstack.yaml \
277277
-e2e.skip-resource-cleanup=false -e2e.use-existing-cluster=true

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackaffinitygroups.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackaffinitygroups.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackclusters.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackclusters.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackfailuredomains.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackfailuredomains.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackisolatednetworks.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackisolatednetworks.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachines.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackmachines.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinestatecheckers.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackmachinestatecheckers.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackmachinetemplates.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackmachinetemplates.infrastructure.cluster.x-k8s.io
109
spec:

‎config/crd/bases/infrastructure.cluster.x-k8s.io_cloudstackzones.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
21
---
32
apiVersion: apiextensions.k8s.io/v1
43
kind: CustomResourceDefinition
54
metadata:
65
annotations:
7-
controller-gen.kubebuilder.io/version: v0.4.1
6+
controller-gen.kubebuilder.io/version: v0.8.0
87
creationTimestamp: null
98
name: cloudstackzones.infrastructure.cluster.x-k8s.io
109
spec:

‎config/rbac/role.yaml

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
---
32
apiVersion: rbac.authorization.k8s.io/v1
43
kind: ClusterRole

‎config/webhook/manifests.yaml

-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
---
32
apiVersion: admissionregistration.k8s.io/v1
43
kind: MutatingWebhookConfiguration
@@ -46,7 +45,6 @@ webhooks:
4645
resources:
4746
- cloudstackmachines
4847
sideEffects: None
49-
5048
---
5149
apiVersion: admissionregistration.k8s.io/v1
5250
kind: ValidatingWebhookConfiguration

‎controllers/utils/affinity_group.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ package utils
1818

1919
import (
2020
"fmt"
21+
"golang.org/x/text/cases"
22+
"golang.org/x/text/language"
2123
"strings"
2224

2325
"github.com/pkg/errors"
@@ -91,6 +93,7 @@ func GenerateAffinityGroupName(csm infrav1.CloudStackMachine, capiMachine *clust
9193
if managerOwnerRef == nil {
9294
return "", errors.Errorf("could not find owner UID for %s/%s", csm.Namespace, csm.Name)
9395
}
96+
titleCaser := cases.Title(language.English)
9497
return fmt.Sprintf("%sAffinity-%s-%s-%s",
95-
strings.Title(csm.Spec.Affinity), managerOwnerRef.Name, managerOwnerRef.UID, csm.Spec.FailureDomainName), nil
98+
titleCaser.String(csm.Spec.Affinity), managerOwnerRef.Name, managerOwnerRef.UID, csm.Spec.FailureDomainName), nil
9699
}

‎go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module sigs.k8s.io/cluster-api-provider-cloudstack
33
go 1.16
44

55
require (
6-
github.com/ReneKroon/ttlcache v1.7.0 // indirect
6+
github.com/ReneKroon/ttlcache v1.7.0
77
github.com/apache/cloudstack-go/v2 v2.13.0
88
github.com/go-logr/logr v1.2.3
99
github.com/golang/mock v1.6.0
@@ -14,6 +14,7 @@ require (
1414
github.com/prometheus/client_golang v1.11.0
1515
github.com/smallfish/simpleyaml v0.1.0
1616
github.com/spf13/pflag v1.0.5
17+
golang.org/x/text v0.3.7
1718
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
1819
k8s.io/api v0.23.0
1920
k8s.io/apimachinery v0.23.0

‎test/e2e/README.md

+12
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,15 @@ For example,
6161
JOB=PR-Blocking make run-e2e
6262
```
6363
This command runs the e2e tests that contains `PR-Blocking` in their spec names.
64+
65+
### Debugging the e2e tests
66+
The E2E tests can be debugged by attaching a debugger to the e2e process after it is launched (*i.e., make run-e2e*).
67+
To facilitate this, the E2E tests can be run with environment variable PAUSE_FOR_DEBUGGER_ATTACH=true.
68+
(This is only strictly needed when you want the debugger to break early in the test process, i.e., in SynchronizedBeforeSuite.
69+
There's usually quite enough time to attach if you're not breaking until your actual test code runs.)
70+
71+
When this environment variable is set to *true* a 15s pause is inserted at the beginning of the test process
72+
(i.e., in the SynchronizedBeforeSuite). The workflow is:
73+
- Launch the e2e test: *PAUSE_FOR_DEBUGGER_ATTACH=true JOB=MyTest make run-e2e*
74+
- Wait for console message: *Pausing 15s so you have a chance to attach a debugger to this process...*
75+
- Quickly attach your debugger to the e2e process (i.e., e2e.test)

‎test/e2e/deploy_app_toxi.go

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
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+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/utils/pointer"
26+
"os"
27+
"path/filepath"
28+
"runtime"
29+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
30+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
31+
"sigs.k8s.io/cluster-api/util"
32+
)
33+
34+
// DeployAppToxiSpec implements a test that verifies that an app deployed to the workload cluster works.
35+
func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput) {
36+
var (
37+
specName = "deploy-app-toxi"
38+
input CommonSpecInput
39+
namespace *corev1.Namespace
40+
cancelWatches context.CancelFunc
41+
clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
42+
bootstrapClusterToxicName string
43+
cloudStackToxicName string
44+
bootstrapClusterToxiProxyContext *helpers.ToxiProxyContext
45+
cloudStackToxiProxyContext *helpers.ToxiProxyContext
46+
appName = "httpd"
47+
appManifestPath = "data/fixture/sample-application.yaml"
48+
expectedHtmlPath = "data/fixture/expected-webpage.html"
49+
appDeploymentReadyTimeout = 180
50+
appPort = 8080
51+
appDefaultHtmlPath = "/"
52+
expectedHtml = ""
53+
clusterName = fmt.Sprintf("%s-%s", specName, util.RandomString(6))
54+
)
55+
56+
BeforeEach(func() {
57+
// ToxiProxy running in a docker container requires docker host networking, only available in linux.
58+
Expect(runtime.GOOS).To(Equal("linux"))
59+
60+
Expect(ctx).NotTo(BeNil(), "ctx is required for %s spec", specName)
61+
input = inputGetter()
62+
Expect(input.E2EConfig).ToNot(BeNil(), "Invalid argument. input.E2EConfig can't be nil when calling %s spec", specName)
63+
Expect(input.ClusterctlConfigPath).To(BeAnExistingFile(), "Invalid argument. input.ClusterctlConfigPath must be an existing file when calling %s spec", specName)
64+
Expect(input.BootstrapClusterProxy).ToNot(BeNil(), "Invalid argument. input.BootstrapClusterProxy can't be nil when calling %s spec", specName)
65+
Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName)
66+
Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion))
67+
68+
// Set up a toxiProxy for bootstrap server.
69+
bootstrapClusterToxiProxyContext = helpers.SetupForToxiProxyTestingBootstrapCluster(input.BootstrapClusterProxy, clusterName)
70+
const ToxicLatencyMs = 100
71+
const ToxicJitterMs = 100
72+
const ToxicToxicity = 1
73+
bootstrapClusterToxicName = bootstrapClusterToxiProxyContext.AddLatencyToxic(ToxicLatencyMs, ToxicJitterMs, ToxicToxicity, false)
74+
75+
// Set up a toxiProxy for CloudStack
76+
cloudStackToxiProxyContext = helpers.SetupForToxiProxyTestingACS(
77+
ctx,
78+
clusterName,
79+
input.BootstrapClusterProxy,
80+
input.E2EConfig,
81+
input.ClusterctlConfigPath,
82+
)
83+
cloudStackToxicName = cloudStackToxiProxyContext.AddLatencyToxic(ToxicLatencyMs, ToxicJitterMs, ToxicToxicity, false)
84+
85+
// Set up a Namespace to host objects for this spec and create a watcher for the namespace events.
86+
namespace, cancelWatches = setupSpecNamespace(ctx, specName, bootstrapClusterToxiProxyContext.ClusterProxy, input.ArtifactFolder)
87+
clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult)
88+
89+
fileContent, err := os.ReadFile(expectedHtmlPath)
90+
Expect(err).To(BeNil(), "Failed to read "+expectedHtmlPath)
91+
expectedHtml = string(fileContent)
92+
})
93+
94+
It("Should be able to download an HTML from the app deployed to the workload cluster", func() {
95+
By("Creating a workload cluster")
96+
97+
flavor := clusterctl.DefaultFlavor
98+
if input.Flavor != nil {
99+
flavor = *input.Flavor
100+
}
101+
namespace := namespace.Name
102+
103+
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
104+
ClusterProxy: bootstrapClusterToxiProxyContext.ClusterProxy,
105+
CNIManifestPath: input.E2EConfig.GetVariable(CNIPath),
106+
ConfigCluster: clusterctl.ConfigClusterInput{
107+
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", bootstrapClusterToxiProxyContext.ClusterProxy.GetName()),
108+
ClusterctlConfigPath: cloudStackToxiProxyContext.ConfigPath,
109+
KubeconfigPath: bootstrapClusterToxiProxyContext.ClusterProxy.GetKubeconfigPath(),
110+
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
111+
Flavor: flavor,
112+
Namespace: namespace,
113+
ClusterName: clusterName,
114+
KubernetesVersion: input.E2EConfig.GetVariable(KubernetesVersion),
115+
ControlPlaneMachineCount: pointer.Int64Ptr(1),
116+
WorkerMachineCount: pointer.Int64Ptr(2),
117+
},
118+
WaitForClusterIntervals: input.E2EConfig.GetIntervals(specName, "wait-cluster"),
119+
WaitForControlPlaneIntervals: input.E2EConfig.GetIntervals(specName, "wait-control-plane"),
120+
WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"),
121+
}, clusterResources)
122+
123+
workloadClusterProxy := bootstrapClusterToxiProxyContext.ClusterProxy.GetWorkloadCluster(ctx, namespace, clusterName)
124+
workloadKubeconfigPath := workloadClusterProxy.GetKubeconfigPath()
125+
126+
appManifestAbsolutePath, _ := filepath.Abs(appManifestPath)
127+
Byf("Deploying a simple web server application to the workload cluster from %s", appManifestAbsolutePath)
128+
Expect(DeployAppToWorkloadClusterAndWaitForDeploymentReady(ctx, workloadKubeconfigPath, appName, appManifestAbsolutePath, appDeploymentReadyTimeout)).To(Succeed())
129+
130+
By("Downloading the default html of the web server")
131+
actualHtml, err := DownloadFromAppInWorkloadCluster(ctx, workloadKubeconfigPath, appName, appPort, appDefaultHtmlPath)
132+
Expect(err).To(BeNil(), "Failed to download")
133+
134+
Expect(actualHtml).To(Equal(expectedHtml))
135+
136+
By("Confirming that the custom reconciliation error metric is scrape-able")
137+
// TODO: Rebuild an E2E test designed purely to test this. Adding this requirement here is too flaky.
138+
// Newer CloudStack instances return a different error code when the ZoneID is missing, and this test
139+
// keeps us from fixing the additional error message that was present when reconciling the Isolated Network
140+
// a bit too soon.
141+
// BIG NOTE: The first reconciliation attempt of isolated_network!AssociatePublicIPAddress() returns
142+
// a CloudStack error 9999. This test expects that to happen.
143+
// No acs_reconciliation_errors appear in the scrape until logged.
144+
// If that error ever gets fixed, this test will break.
145+
// metricsScrape, err := DownloadMetricsFromCAPCManager(ctx, input.BootstrapClusterProxy.GetKubeconfigPath())
146+
// Expect(err).To(BeNil())
147+
// Expect(metricsScrape).To(MatchRegexp("acs_reconciliation_errors\\{acs_error_code=\"9999\"\\} [0-9]+"))
148+
By("PASSED!")
149+
})
150+
151+
AfterEach(func() {
152+
// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
153+
dumpSpecResourcesAndCleanup(ctx, specName, bootstrapClusterToxiProxyContext.ClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
154+
155+
// Tear down the ToxiProxies
156+
cloudStackToxiProxyContext.RemoveToxic(cloudStackToxicName)
157+
helpers.TearDownToxiProxyACS(ctx, input.BootstrapClusterProxy, cloudStackToxiProxyContext)
158+
159+
bootstrapClusterToxiProxyContext.RemoveToxic(bootstrapClusterToxicName)
160+
helpers.TearDownToxiProxyBootstrap(bootstrapClusterToxiProxyContext)
161+
})
162+
}

‎test/e2e/deploy_app_toxi_test.go

+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 slow network [ToxiProxy]", func() {
28+
29+
DeployAppToxiSpec(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+
})

‎test/e2e/e2e_suite_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ import (
2424
"fmt"
2525
"os"
2626
"path/filepath"
27+
go_runtime "runtime"
28+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
2729
"strings"
2830
"testing"
31+
"time"
2932

3033
. "github.com/onsi/ginkgo"
3134
. "github.com/onsi/gomega"
@@ -102,6 +105,11 @@ func TestE2E(t *testing.T) {
102105
var _ = SynchronizedBeforeSuite(func() []byte {
103106
// Before all ParallelNodes.
104107

108+
if os.Getenv("PAUSE_FOR_DEBUGGER_ATTACH") == "true" {
109+
By("Pausing 15s so you have a chance to attach a debugger to this process...")
110+
time.Sleep(15 * time.Second)
111+
}
112+
105113
Expect(configPath).To(BeAnExistingFile(), "Invalid test suite argument. e2e.config should be an existing file.")
106114
Expect(os.MkdirAll(artifactFolder, 0755)).To(Succeed(), "Invalid test suite argument. Can't create e2e.artifacts-folder %q", artifactFolder) //nolint:gosec
107115

@@ -111,6 +119,12 @@ var _ = SynchronizedBeforeSuite(func() []byte {
111119
Byf("Loading the e2e test configuration from %q", configPath)
112120
e2eConfig = loadE2EConfig(configPath)
113121

122+
// Toxiproxy running in a docker container requires docker host networking, only available in linux.
123+
if go_runtime.GOOS == "linux" {
124+
By("Launching Toxiproxy Server")
125+
Expect(helpers.ToxiProxyServerExec(ctx)).To(Succeed())
126+
}
127+
114128
if clusterctlConfig == "" {
115129
Byf("Creating a clusterctl local repository into %q", artifactFolder)
116130
clusterctlConfigPath = createClusterctlLocalRepository(e2eConfig, filepath.Join(artifactFolder, "repository"))
@@ -163,6 +177,11 @@ var _ = SynchronizedAfterSuite(func() {
163177
if !skipCleanup {
164178
tearDown(bootstrapClusterProvider, bootstrapClusterProxy)
165179
}
180+
181+
if go_runtime.GOOS == "linux" {
182+
By("Killing Toxiproxy Server")
183+
Expect(helpers.ToxiProxyServerKill(ctx)).To(Succeed())
184+
}
166185
})
167186

168187
func initScheme() *runtime.Scheme {

‎test/e2e/go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ module sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e
33
go 1.16
44

55
require (
6+
github.com/Shopify/toxiproxy/v2 v2.5.0
67
github.com/apache/cloudstack-go/v2 v2.12.0
78
github.com/blang/semver v3.5.1+incompatible
89
github.com/onsi/ginkgo v1.16.5
910
github.com/onsi/gomega v1.17.0
10-
gopkg.in/ini.v1 v1.63.2
11+
gopkg.in/yaml.v3 v3.0.1
1112
k8s.io/api v0.23.0
1213
k8s.io/apimachinery v0.23.0
1314
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b

‎test/e2e/go.sum

+50-14
Large diffs are not rendered by default.

‎test/e2e/helpers/data/README

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
For simplicity of diffing in the test, the kubeconfig file in this directory has been formatted to match the
2+
whitespacing done by golang's default map[string]interface{} marshaller (which is not they way many actual
3+
kubeconfigs are typically whitespaced).

‎test/e2e/helpers/data/kubeconfig

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
apiVersion: v1
2+
clusters:
3+
- cluster:
4+
certificate-authority-data: LS0tLS1==
5+
server: https://008DEB5B0A4AB3DF7594BEDB9C185602.gr7.us-east-2.eks.amazonaws.com
6+
name: guestbook.us-east-2.eksctl.io
7+
- cluster:
8+
certificate-authority-data: LS0tLS1==
9+
server: https://568592C67CC6996CCE8E826617A05E34.gr7.us-east-2.eks.amazonaws.com
10+
name: user-5368.us-east-2.eksctl.io
11+
- cluster:
12+
certificate-authority-data: LS0tLS1==
13+
server: https://127.0.0.1:64927
14+
name: kind-capi-test
15+
contexts:
16+
- context:
17+
cluster: guestbook.us-east-2.eksctl.io
18+
user: user-Isengard@guestbook.us-east-2.eksctl.io
19+
name: user-Isengard@guestbook.us-east-2.eksctl.io
20+
- context:
21+
cluster: user-5368.us-east-2.eksctl.io
22+
user: user-Isengard@user-5368.us-east-2.eksctl.io
23+
name: user-Isengard@user-5368.us-east-2.eksctl.io
24+
- context:
25+
cluster: kind-capi-test
26+
user: kind-capi-test
27+
name: kind-capi-test
28+
current-context: kind-capi-test
29+
kind: Config
30+
preferences: {}
31+
users:
32+
- name: user-Isengard@guestbook.us-east-2.eksctl.io
33+
user:
34+
exec:
35+
apiVersion: client.authentication.k8s.io/v1alpha1
36+
args:
37+
- token
38+
- -i
39+
- guestbook
40+
command: aws-iam-authenticator
41+
env:
42+
- name: AWS_STS_REGIONAL_ENDPOINTS
43+
value: regional
44+
- name: AWS_DEFAULT_REGION
45+
value: us-east-2
46+
interactiveMode: IfAvailable
47+
provideClusterInfo: false
48+
- name: user-Isengard@user-5368.us-east-2.eksctl.io
49+
user:
50+
exec:
51+
apiVersion: client.authentication.k8s.io/v1alpha1
52+
args:
53+
- token
54+
- -i
55+
- user-5368
56+
command: aws-iam-authenticator
57+
env:
58+
- name: AWS_STS_REGIONAL_ENDPOINTS
59+
value: regional
60+
- name: AWS_DEFAULT_REGION
61+
value: us-east-2
62+
interactiveMode: IfAvailable
63+
provideClusterInfo: false
64+
- name: kind-capi-test
65+
user:
66+
client-certificate-data: LS0tL==
67+
client-key-data: LS0tLS==

‎test/e2e/helpers/kubeconfig.go

+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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 (
20+
"errors"
21+
"gopkg.in/yaml.v3"
22+
"io/ioutil"
23+
)
24+
25+
type Kubeconfig struct {
26+
content map[string]interface{}
27+
}
28+
29+
func NewKubeconfig() *Kubeconfig {
30+
return &Kubeconfig{}
31+
}
32+
33+
func (k *Kubeconfig) Load(path string) error {
34+
rawContent, err := ioutil.ReadFile(path)
35+
if err != nil {
36+
return err
37+
}
38+
39+
var content interface{}
40+
if err := yaml.Unmarshal(rawContent, &content); err != nil {
41+
return err
42+
}
43+
44+
mapContent, ok := content.(map[string]interface{})
45+
if ok == false {
46+
return errors.New("kubeconfig unmarshalling didn't provide expected type map[string]interface{}")
47+
}
48+
49+
k.content = mapContent
50+
return nil
51+
}
52+
53+
func (k *Kubeconfig) Save(path string) error {
54+
rawContent, err := yaml.Marshal(k.content)
55+
if err != nil {
56+
return err
57+
}
58+
59+
err = ioutil.WriteFile(path, rawContent, 0644)
60+
if err != nil {
61+
return err
62+
}
63+
64+
return err
65+
}
66+
67+
func (k *Kubeconfig) GetCurrentContextName() (string, error) {
68+
value, present := k.content["current-context"]
69+
if present == false {
70+
return "", errors.New("current context not present")
71+
}
72+
73+
castValue, ok := value.(string)
74+
if ok == false {
75+
return "", errors.New("current content not unmarshalled as a string")
76+
}
77+
78+
return castValue, nil
79+
}
80+
81+
func (k *Kubeconfig) GetCurrentContext() (map[string]interface{}, error) {
82+
currentContextName, err := k.GetCurrentContextName()
83+
if err != nil {
84+
return nil, err
85+
}
86+
87+
contexts := k.content["contexts"].([]interface{})
88+
var currentContextArrayEntry map[string]interface{} = nil
89+
for _, ctx := range contexts {
90+
castCtxArrayEntry, ok := ctx.(map[string]interface{})
91+
if ok != true {
92+
return nil, errors.New("unmarshalled kubeconfig context array entry not of expected type map[string]interface{}")
93+
}
94+
if castCtxArrayEntry["name"] == currentContextName {
95+
currentContextArrayEntry = ctx.(map[string]interface{})
96+
break
97+
}
98+
}
99+
if currentContextArrayEntry == nil {
100+
return nil, errors.New("no context matching current context name exists in kubeconfig contexts")
101+
}
102+
103+
currentContext, present := currentContextArrayEntry["context"]
104+
if present == false {
105+
return nil, errors.New("context object not found in matched context array object")
106+
}
107+
108+
castCurrentContext, ok := currentContext.(map[string]interface{})
109+
if ok != true {
110+
return nil, errors.New("unmarshalled kubeconfig context not of expected type map[string]interface{}")
111+
}
112+
113+
return castCurrentContext, nil
114+
}
115+
116+
func (k *Kubeconfig) GetCurrentClusterName() (string, error) {
117+
currentContext, err := k.GetCurrentContext()
118+
if err != nil {
119+
return "", err
120+
}
121+
122+
clusterName, present := currentContext["cluster"]
123+
if present == false {
124+
return "", errors.New("cluster name not found in current context")
125+
}
126+
127+
castClusterName, ok := clusterName.(string)
128+
if ok == false {
129+
return "", errors.New("context's cluster name not unmarshalled a string")
130+
}
131+
132+
return castClusterName, nil
133+
}
134+
135+
func (k *Kubeconfig) GetCurrentCluster() (map[string]interface{}, error) {
136+
currentClusterName, err := k.GetCurrentClusterName()
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
clusters := k.content["clusters"].([]interface{})
142+
var currentClusterArrayEntry map[string]interface{} = nil
143+
for _, clu := range clusters {
144+
castClusterArrayEntry, ok := clu.(map[string]interface{})
145+
if ok != true {
146+
return nil, errors.New("unmarshalled kubeconfig cluster array entry not of expected type map[string]interface{}")
147+
}
148+
if castClusterArrayEntry["name"] == currentClusterName {
149+
currentClusterArrayEntry = clu.(map[string]interface{})
150+
break
151+
}
152+
}
153+
if currentClusterArrayEntry == nil {
154+
return nil, errors.New("no cluster matching cluster name specified in current context exists in kubeconfig contexts")
155+
}
156+
157+
currentCluster, present := currentClusterArrayEntry["cluster"]
158+
if present == false {
159+
return nil, errors.New("cluster object not found in matched cluster array entry")
160+
}
161+
162+
castCurrentCluster, ok := currentCluster.(map[string]interface{})
163+
if ok != true {
164+
return nil, errors.New("unmarshalled kubeconfig cluster not of expected type map[string]interface{}")
165+
}
166+
167+
return castCurrentCluster, nil
168+
}
169+
170+
func (k *Kubeconfig) GetCurrentServer() (string, error) {
171+
172+
currentCluster, err := k.GetCurrentCluster()
173+
if err != nil {
174+
return "", err
175+
}
176+
177+
server, present := currentCluster["server"]
178+
if present == false {
179+
return "", errors.New("server attribute not present in current cluster")
180+
}
181+
182+
castServer, ok := server.(string)
183+
if ok == false {
184+
return "", errors.New("unmarshalled server not of expected type string")
185+
}
186+
187+
return castServer, nil
188+
}
189+
190+
func (k *Kubeconfig) SetCurrentServer(newServer string) error {
191+
currentCluster, err := k.GetCurrentCluster()
192+
if err != nil {
193+
return err
194+
}
195+
196+
var newServerUntyped interface{} = newServer
197+
currentCluster["server"] = newServerUntyped
198+
199+
return nil
200+
}

‎test/e2e/helpers/kubeconfig_test.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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_test
18+
19+
import (
20+
. "github.com/onsi/ginkgo"
21+
. "github.com/onsi/gomega"
22+
"io/ioutil"
23+
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
24+
)
25+
26+
var _ = Describe("Test kubeconfig helper methods", func() {
27+
It("should work", func() {
28+
kubeconfig := helpers.NewKubeconfig()
29+
30+
var kubeconfigPath string = "./data/kubeconfig"
31+
var unmodifiedKubeconfigPath string = "/tmp/unmodifiedKubeconfig"
32+
Ω(kubeconfig.Load(kubeconfigPath)).Should(Succeed())
33+
Ω(kubeconfig.Save(unmodifiedKubeconfigPath)).Should(Succeed())
34+
35+
originalKubeconfig, err := ioutil.ReadFile(kubeconfigPath)
36+
Ω(err).ShouldNot(HaveOccurred())
37+
rewrittenKubeconfig, err := ioutil.ReadFile(unmodifiedKubeconfigPath)
38+
Ω(err).ShouldNot(HaveOccurred())
39+
Ω(rewrittenKubeconfig).Should(Equal(originalKubeconfig))
40+
41+
currentContextName, err := kubeconfig.GetCurrentContextName()
42+
Ω(err).ShouldNot(HaveOccurred())
43+
Ω(currentContextName).Should(Equal("kind-capi-test"))
44+
45+
_, err = kubeconfig.GetCurrentContext()
46+
Ω(err).ShouldNot(HaveOccurred())
47+
48+
currentClusterName, err := kubeconfig.GetCurrentClusterName()
49+
Ω(err).ShouldNot(HaveOccurred())
50+
Ω(currentClusterName).Should(Equal("kind-capi-test"))
51+
52+
currentCluster, err := kubeconfig.GetCurrentCluster()
53+
Ω(err).ShouldNot(HaveOccurred())
54+
Ω(currentCluster).ShouldNot(BeEmpty())
55+
56+
currentServer, err := kubeconfig.GetCurrentServer()
57+
Ω(err).ShouldNot(HaveOccurred())
58+
Ω(currentServer).Should(Equal("https://127.0.0.1:64927"))
59+
60+
var newServerUrl string = "\"https://myTestServer:12345\""
61+
kubeconfig.SetCurrentServer(newServerUrl)
62+
63+
var modifiedKubeconfigPath string = "/tmp/modifiedKubeconfig.yaml"
64+
Ω(kubeconfig.Save(modifiedKubeconfigPath)).Should(Succeed())
65+
Ω(modifiedKubeconfigPath).Should(BeAnExistingFile())
66+
67+
modifiedKubeconfigContent, err := ioutil.ReadFile(modifiedKubeconfigPath)
68+
Ω(err).ShouldNot(HaveOccurred())
69+
Ω(string(modifiedKubeconfigContent)).Should(ContainSubstring(newServerUrl))
70+
71+
})
72+
})

‎test/e2e/helpers/suite_test.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package helpers_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/onsi/ginkgo"
7+
. "github.com/onsi/gomega"
8+
)
9+
10+
func TestHelpers(t *testing.T) {
11+
RegisterFailHandler(Fail)
12+
RunSpecs(t, "Helpers Suite")
13+
}

‎test/e2e/helpers/toxiProxy.go

+254
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
package helpers
2+
3+
import (
4+
"context"
5+
"fmt"
6+
toxiproxyapi "github.com/Shopify/toxiproxy/v2/client"
7+
. "github.com/onsi/gomega"
8+
corev1 "k8s.io/api/core/v1"
9+
"net"
10+
"os"
11+
"path"
12+
"regexp"
13+
"sigs.k8s.io/cluster-api/test/framework"
14+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
15+
"sigs.k8s.io/cluster-api/test/framework/exec"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
17+
"strconv"
18+
"strings"
19+
)
20+
21+
func ToxiProxyServerExec(ctx context.Context) error {
22+
execArgs := []string{"run", "-d", "--name=capc-e2e-toxiproxy", "--net=host", "--rm", "ghcr.io/shopify/toxiproxy"}
23+
runCmd := exec.NewCommand(
24+
exec.WithCommand("docker"),
25+
exec.WithArgs(execArgs...),
26+
)
27+
_, stderr, err := runCmd.Run(ctx)
28+
if err != nil {
29+
fmt.Println(string(stderr))
30+
}
31+
return err
32+
}
33+
34+
func ToxiProxyServerKill(ctx context.Context) error {
35+
execArgs := []string{"stop", "capc-e2e-toxiproxy"}
36+
runCmd := exec.NewCommand(
37+
exec.WithCommand("docker"),
38+
exec.WithArgs(execArgs...),
39+
)
40+
_, _, err := runCmd.Run(ctx)
41+
return err
42+
}
43+
44+
type ToxiProxyContext struct {
45+
KubeconfigPath string
46+
Secret corev1.Secret
47+
ClusterProxy framework.ClusterProxy
48+
ToxiProxy *toxiproxyapi.Proxy
49+
ConfigPath string
50+
}
51+
52+
func SetupForToxiProxyTestingBootstrapCluster(bootstrapClusterProxy framework.ClusterProxy, clusterName string) *ToxiProxyContext {
53+
// Read/parse the actual kubeconfig for the cluster
54+
kubeConfig := NewKubeconfig()
55+
unproxiedKubeconfigPath := bootstrapClusterProxy.GetKubeconfigPath()
56+
err := kubeConfig.Load(unproxiedKubeconfigPath)
57+
Expect(err).To(BeNil())
58+
59+
// Get the cluster's server url from the kubeconfig
60+
server, err := kubeConfig.GetCurrentServer()
61+
Expect(err).To(BeNil())
62+
63+
// Decompose server url into protocol, address and port
64+
protocol, address, port, _ := parseUrl(server)
65+
66+
// Format into the needed addresses/URL form
67+
actualBootstrapClusterAddress := fmt.Sprintf("%v:%v", address, port)
68+
69+
// Create the toxiProxy for this test
70+
toxiProxyClient := toxiproxyapi.NewClient("127.0.0.1:8474")
71+
toxiProxyName := fmt.Sprintf("deploy_app_toxi_test_%v_bootstrap", clusterName)
72+
proxy, err := toxiProxyClient.CreateProxy(toxiProxyName, "127.0.0.1:0", actualBootstrapClusterAddress)
73+
Expect(err).To(BeNil())
74+
75+
// Get the actual listen address (having the toxiproxy-assigned port #).
76+
toxiProxyServerUrl := fmt.Sprintf("%v://%v", protocol, proxy.Listen)
77+
78+
// Modify the kubeconfig to use the toxiproxy's server url
79+
err = kubeConfig.SetCurrentServer(toxiProxyServerUrl)
80+
Expect(err).To(BeNil())
81+
82+
// Write the modified kubeconfig using a new name.
83+
extension := path.Ext(unproxiedKubeconfigPath)
84+
baseWithoutExtension := strings.TrimSuffix(path.Base(unproxiedKubeconfigPath), extension)
85+
toxiProxyKubeconfigFileName := fmt.Sprintf("toxiProxy_%v_%v%v", baseWithoutExtension, clusterName, extension)
86+
toxiProxyKubeconfigPath := path.Join("/tmp", toxiProxyKubeconfigFileName)
87+
err = kubeConfig.Save(toxiProxyKubeconfigPath)
88+
Expect(err).To(BeNil())
89+
90+
// Create a new ClusterProxy using the new kubeconfig
91+
toxiproxyBootstrapClusterProxy := framework.NewClusterProxy(
92+
"toxiproxy-bootstrap",
93+
toxiProxyKubeconfigPath,
94+
bootstrapClusterProxy.GetScheme(),
95+
framework.WithMachineLogCollector(framework.DockerLogCollector{}),
96+
)
97+
98+
return &ToxiProxyContext{
99+
KubeconfigPath: toxiProxyKubeconfigPath,
100+
ClusterProxy: toxiproxyBootstrapClusterProxy,
101+
ToxiProxy: proxy,
102+
}
103+
}
104+
105+
func TearDownToxiProxyBootstrap(toxiProxyContext *ToxiProxyContext) {
106+
// Tear down the proxy
107+
err := toxiProxyContext.ToxiProxy.Delete()
108+
Expect(err).To(BeNil())
109+
110+
// Delete the kubeconfig pointing to the proxy
111+
err = os.Remove(toxiProxyContext.KubeconfigPath)
112+
Expect(err).To(BeNil())
113+
}
114+
115+
func (tp *ToxiProxyContext) RemoveToxic(toxicName string) {
116+
err := tp.ToxiProxy.RemoveToxic(toxicName)
117+
Expect(err).To(BeNil())
118+
}
119+
120+
func (tp *ToxiProxyContext) AddLatencyToxic(latencyMs int, jitterMs int, toxicity float32, upstream bool) string {
121+
stream := "downstream"
122+
if upstream == true {
123+
stream = "upstream"
124+
}
125+
toxicName := fmt.Sprintf("latency_%v", stream)
126+
127+
_, err := tp.ToxiProxy.AddToxic(toxicName, "latency", stream, toxicity, toxiproxyapi.Attributes{
128+
"latency": latencyMs,
129+
"jitter": jitterMs,
130+
})
131+
Expect(err).To(BeNil())
132+
133+
return toxicName
134+
}
135+
136+
func SetupForToxiProxyTestingACS(ctx context.Context, clusterName string, clusterProxy framework.ClusterProxy, e2eConfig *clusterctl.E2EConfig, configPath string) *ToxiProxyContext {
137+
// Get the cloud-config secret that CAPC will use to access CloudStack
138+
fdEndpointSecretObjectKey := client.ObjectKey{
139+
Namespace: e2eConfig.GetVariable("CLOUDSTACK_FD1_SECRET_NAMESPACE"),
140+
Name: e2eConfig.GetVariable("CLOUDSTACK_FD1_SECRET_NAME"),
141+
}
142+
fdEndpointSecret := corev1.Secret{}
143+
err := clusterProxy.GetClient().Get(ctx, fdEndpointSecretObjectKey, &fdEndpointSecret)
144+
Expect(err).To(BeNil())
145+
146+
// Extract and parse the URL for CloudStack from the secret
147+
cloudstackUrl := string(fdEndpointSecret.Data["api-url"])
148+
protocol, address, port, path := parseUrl(cloudstackUrl)
149+
upstreamAddress := fmt.Sprintf("%v:%v", address, port)
150+
151+
// Create the CloudStack toxiProxy for this test
152+
toxiProxyClient := toxiproxyapi.NewClient("127.0.0.1:8474")
153+
toxiProxyName := fmt.Sprintf("%v_cloudstack", clusterName)
154+
155+
// Formulate the proxy listen address.
156+
// CAPC can't route to the actual host's localhost. We have to use a real host IP address for the proxy listen address.
157+
hostIP := getOutboundIP()
158+
proxyAddress := fmt.Sprintf("%v:0", hostIP)
159+
proxy, err := toxiProxyClient.CreateProxy(toxiProxyName, proxyAddress, upstreamAddress)
160+
Expect(err).To(BeNil())
161+
162+
// Retrieve the actual listen address (having the toxiproxy-assigned port #).
163+
toxiProxyUrl := fmt.Sprintf("%v://%v%v", protocol, proxy.Listen, path)
164+
165+
// Create a new cloud-config secret using the proxy listen address
166+
toxiProxyFdEndpointSecret := corev1.Secret{}
167+
toxiProxyFdEndpointSecret.Type = fdEndpointSecret.Type
168+
toxiProxyFdEndpointSecret.Namespace = fdEndpointSecret.Namespace
169+
toxiProxyFdEndpointSecret.Name = fdEndpointSecret.Name + "-toxiproxy"
170+
toxiProxyFdEndpointSecret.Data = make(map[string][]byte)
171+
toxiProxyFdEndpointSecret.Data["api-key"] = fdEndpointSecret.Data["api-key"]
172+
toxiProxyFdEndpointSecret.Data["secret-key"] = fdEndpointSecret.Data["secret-key"]
173+
toxiProxyFdEndpointSecret.Data["verify-ssl"] = fdEndpointSecret.Data["verify-ssl"]
174+
toxiProxyFdEndpointSecret.Data["api-url"] = []byte(toxiProxyUrl)
175+
176+
err = clusterProxy.GetClient().Create(ctx, &toxiProxyFdEndpointSecret)
177+
Expect(err).To(BeNil())
178+
179+
// Override the test config to use this alternate cloud-config secret
180+
e2eConfig.Variables["CLOUDSTACK_FD1_SECRET_NAME"] = toxiProxyFdEndpointSecret.Name
181+
182+
// Overriding e2e config file into a new temp copy, so as not to inadvertently override the other e2e tests.
183+
newConfigFilePath := fmt.Sprintf("/tmp/%v.yaml", toxiProxyName)
184+
editConfigFile(newConfigFilePath, configPath, "CLOUDSTACK_FD1_SECRET_NAME", toxiProxyFdEndpointSecret.Name)
185+
186+
// Return a context
187+
return &ToxiProxyContext{
188+
Secret: toxiProxyFdEndpointSecret,
189+
ToxiProxy: proxy,
190+
ConfigPath: newConfigFilePath,
191+
}
192+
}
193+
194+
func TearDownToxiProxyACS(ctx context.Context, clusterProxy framework.ClusterProxy, toxiProxyContext *ToxiProxyContext) {
195+
// Tear down the proxy
196+
err := toxiProxyContext.ToxiProxy.Delete()
197+
Expect(err).To(BeNil())
198+
199+
// Delete the secret
200+
err = clusterProxy.GetClient().Delete(ctx, &toxiProxyContext.Secret)
201+
Expect(err).To(BeNil())
202+
203+
// Delete the overridden e2e config
204+
err = os.Remove(toxiProxyContext.ConfigPath)
205+
Expect(err).To(BeNil())
206+
207+
}
208+
209+
func parseUrl(url string) (string, string, int, string) {
210+
serverRegex := regexp.MustCompilePOSIX("(https?)://([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):([0-9]+)?(.*)")
211+
212+
urlComponents := serverRegex.FindStringSubmatch(url)
213+
Expect(len(urlComponents)).To(BeNumerically(">=", 4))
214+
protocol := urlComponents[1]
215+
address := urlComponents[2]
216+
port, err := strconv.Atoi(urlComponents[3])
217+
Expect(err).To(BeNil())
218+
path := urlComponents[4]
219+
return protocol, address, port, path
220+
}
221+
222+
func getOutboundIP() net.IP {
223+
conn, err := net.Dial("udp", "8.8.8.8:80") // 8.8.8.8:80 is arbitrary. Any IP will do, reachable or not.
224+
Expect(err).To(BeNil())
225+
226+
defer conn.Close()
227+
228+
localAddr := conn.LocalAddr().(*net.UDPAddr)
229+
230+
return localAddr.IP
231+
}
232+
233+
func editConfigFile(destFilename string, sourceFilename string, key string, newValue string) {
234+
// For config files with key: value on each line.
235+
236+
dat, err := os.ReadFile(sourceFilename)
237+
Expect(err).To(BeNil())
238+
239+
lines := strings.Split(string(dat), "\n")
240+
241+
keyFound := false
242+
for index, line := range lines {
243+
if strings.HasPrefix(line, "CLOUDSTACK_FD1_SECRET_NAME:") {
244+
keyFound = true
245+
lines[index] = fmt.Sprintf("%v: %v", key, newValue)
246+
break
247+
}
248+
}
249+
Expect(keyFound).To(BeTrue())
250+
251+
dat = []byte(strings.Join(lines[:], "\n"))
252+
err = os.WriteFile(destFilename, dat, 0600)
253+
Expect(err).To(BeNil())
254+
}

0 commit comments

Comments
 (0)
Please sign in to comment.