Skip to content

Commit 54ca6e1

Browse files
committed
Injecting toxiproxy between CAPC and ACS
1 parent bb17e90 commit 54ca6e1

File tree

4 files changed

+194
-48
lines changed

4 files changed

+194
-48
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
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

test/e2e/deploy_app_toxi.go

Lines changed: 61 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"path/filepath"
2828
"runtime"
2929
"sigs.k8s.io/cluster-api-provider-cloudstack-staging/test/e2e/helpers"
30+
"sigs.k8s.io/controller-runtime/pkg/client"
3031

3132
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
3233
"sigs.k8s.io/cluster-api/util"
@@ -35,20 +36,23 @@ import (
3536
// DeployAppToxiSpec implements a test that verifies that an app deployed to the workload cluster works.
3637
func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput) {
3738
var (
38-
specName = "deploy-app-toxi"
39-
input CommonSpecInput
40-
namespace *corev1.Namespace
41-
cancelWatches context.CancelFunc
42-
clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
43-
toxicName string
44-
toxiProxyContext *helpers.ToxiProxyContext = nil
45-
appName = "httpd"
46-
appManifestPath = "data/fixture/sample-application.yaml"
47-
expectedHtmlPath = "data/fixture/expected-webpage.html"
48-
appDeploymentReadyTimeout = 180
49-
appPort = 8080
50-
appDefaultHtmlPath = "/"
51-
expectedHtml = ""
39+
specName = "deploy-app-toxi"
40+
input CommonSpecInput
41+
namespace *corev1.Namespace
42+
cancelWatches context.CancelFunc
43+
clusterResources *clusterctl.ApplyClusterTemplateAndWaitResult
44+
bootstrapClusterToxicName string
45+
cloudStackToxicName string
46+
bootstrapClusterToxiProxyContext *helpers.ToxiProxyContext
47+
cloudStackToxiProxyContext *helpers.ToxiProxyContext
48+
appName = "httpd"
49+
appManifestPath = "data/fixture/sample-application.yaml"
50+
expectedHtmlPath = "data/fixture/expected-webpage.html"
51+
appDeploymentReadyTimeout = 180
52+
appPort = 8080
53+
appDefaultHtmlPath = "/"
54+
expectedHtml = ""
55+
clusterName = fmt.Sprintf("%s-%s", specName, util.RandomString(6))
5256
)
5357

5458
BeforeEach(func() {
@@ -63,15 +67,25 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
6367
Expect(os.MkdirAll(input.ArtifactFolder, 0750)).To(Succeed(), "Invalid argument. input.ArtifactFolder can't be created for %s spec", specName)
6468
Expect(input.E2EConfig.Variables).To(HaveKey(KubernetesVersion))
6569

66-
// Setup a toxiProxy for this test.
67-
toxiProxyContext = helpers.SetupForToxiProxyTesting(input.BootstrapClusterProxy)
68-
const TOXIC_LATENCY_MS = 100
69-
const TOXIC_JITTER_MS = 100
70-
const TOXIC_TOXICITY = 1
71-
toxicName = toxiProxyContext.AddLatencyToxic(TOXIC_LATENCY_MS, TOXIC_JITTER_MS, TOXIC_TOXICITY, false)
72-
73-
// Setup a Namespace where to host objects for this spec and create a watcher for the namespace events.
74-
namespace, cancelWatches = setupSpecNamespace(ctx, specName, toxiProxyContext.ClusterProxy, input.ArtifactFolder)
70+
// Set up a toxiProxy for bootstrap server.
71+
bootstrapClusterToxiProxyContext = helpers.SetupForToxiProxyTestingBootstrapCluster(input.BootstrapClusterProxy, clusterName)
72+
const ToxicLatencyMs = 100
73+
const ToxicJitterMs = 100
74+
const ToxicToxicity = 1
75+
bootstrapClusterToxicName = bootstrapClusterToxiProxyContext.AddLatencyToxic(ToxicLatencyMs, ToxicJitterMs, ToxicToxicity, false)
76+
77+
// Set up a toxiProxy for CloudStack
78+
cloudStackToxiProxyContext = helpers.SetupForToxiProxyTestingACS(
79+
ctx,
80+
clusterName,
81+
input.BootstrapClusterProxy,
82+
input.E2EConfig,
83+
input.ClusterctlConfigPath,
84+
)
85+
cloudStackToxicName = cloudStackToxiProxyContext.AddLatencyToxic(ToxicLatencyMs, ToxicJitterMs, ToxicToxicity, false)
86+
87+
// Set up a Namespace to host objects for this spec and create a watcher for the namespace events.
88+
namespace, cancelWatches = setupSpecNamespace(ctx, specName, bootstrapClusterToxiProxyContext.ClusterProxy, input.ArtifactFolder)
7589
clusterResources = new(clusterctl.ApplyClusterTemplateAndWaitResult)
7690

7791
fileContent, err := os.ReadFile(expectedHtmlPath)
@@ -87,15 +101,14 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
87101
flavor = *input.Flavor
88102
}
89103
namespace := namespace.Name
90-
clusterName := fmt.Sprintf("%s-%s", specName, util.RandomString(6))
91104

92105
clusterctl.ApplyClusterTemplateAndWait(ctx, clusterctl.ApplyClusterTemplateAndWaitInput{
93-
ClusterProxy: toxiProxyContext.ClusterProxy,
106+
ClusterProxy: bootstrapClusterToxiProxyContext.ClusterProxy,
94107
CNIManifestPath: input.E2EConfig.GetVariable(CNIPath),
95108
ConfigCluster: clusterctl.ConfigClusterInput{
96-
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", toxiProxyContext.ClusterProxy.GetName()),
97-
ClusterctlConfigPath: input.ClusterctlConfigPath,
98-
KubeconfigPath: toxiProxyContext.ClusterProxy.GetKubeconfigPath(),
109+
LogFolder: filepath.Join(input.ArtifactFolder, "clusters", bootstrapClusterToxiProxyContext.ClusterProxy.GetName()),
110+
ClusterctlConfigPath: cloudStackToxiProxyContext.ConfigPath,
111+
KubeconfigPath: bootstrapClusterToxiProxyContext.ClusterProxy.GetKubeconfigPath(),
99112
InfrastructureProvider: clusterctl.DefaultInfrastructureProvider,
100113
Flavor: flavor,
101114
Namespace: namespace,
@@ -109,7 +122,7 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
109122
WaitForMachineDeployments: input.E2EConfig.GetIntervals(specName, "wait-worker-nodes"),
110123
}, clusterResources)
111124

112-
workloadClusterProxy := toxiProxyContext.ClusterProxy.GetWorkloadCluster(ctx, namespace, clusterName)
125+
workloadClusterProxy := bootstrapClusterToxiProxyContext.ClusterProxy.GetWorkloadCluster(ctx, namespace, clusterName)
113126
workloadKubeconfigPath := workloadClusterProxy.GetKubeconfigPath()
114127

115128
appManifestAbsolutePath, _ := filepath.Abs(appManifestPath)
@@ -139,10 +152,25 @@ func DeployAppToxiSpec(ctx context.Context, inputGetter func() CommonSpecInput)
139152

140153
AfterEach(func() {
141154
// Dumps all the resources in the spec namespace, then cleanups the cluster object and the spec namespace itself.
142-
dumpSpecResourcesAndCleanup(ctx, specName, toxiProxyContext.ClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
155+
dumpSpecResourcesAndCleanup(ctx, specName, bootstrapClusterToxiProxyContext.ClusterProxy, input.ArtifactFolder, namespace, cancelWatches, clusterResources.Cluster, input.E2EConfig.GetIntervals, input.SkipCleanup)
156+
157+
// Tear down the ToxiProxies
158+
cloudStackToxiProxyContext.RemoveToxic(cloudStackToxicName)
159+
helpers.TearDownToxiProxyACS(ctx, input.BootstrapClusterProxy, cloudStackToxiProxyContext)
143160

144-
// Tear down the proxy
145-
toxiProxyContext.RemoveToxic(toxicName)
146-
helpers.TearDownToxiProxy(toxiProxyContext)
161+
bootstrapClusterToxiProxyContext.RemoveToxic(bootstrapClusterToxicName)
162+
helpers.TearDownToxiProxyBootstrap(bootstrapClusterToxiProxyContext)
147163
})
148164
}
165+
166+
func getFailureDomainEndpointSecret(ctx context.Context) corev1.Secret {
167+
fdEndpointSecretObjectKey := client.ObjectKey{
168+
Namespace: input.E2EConfig.GetVariable("CLOUDSTACK_FD1_SECRET_NAMESPACE"),
169+
Name: input.E2EConfig.GetVariable("CLOUDSTACK_FD1_SECRET_NAME"),
170+
}
171+
fdEndpointSecret := corev1.Secret{}
172+
err := input.BootstrapClusterProxy.GetClient().Get(ctx, fdEndpointSecretObjectKey, &fdEndpointSecret)
173+
Expect(err).To(BeNil())
174+
175+
return fdEndpointSecret
176+
}

test/e2e/e2e_suite_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func TestE2E(t *testing.T) {
105105
var _ = SynchronizedBeforeSuite(func() []byte {
106106
// Before all ParallelNodes.
107107

108-
if (os.Getenv("PAUSE_FOR_DEBUGGER_ATTACH") == "true") {
108+
if os.Getenv("PAUSE_FOR_DEBUGGER_ATTACH") == "true" {
109109
By("Pausing 15s so you have a chance to attach a debugger to this process...")
110110
time.Sleep(15 * time.Second)
111111
}

test/e2e/helpers/toxiProxy.go

Lines changed: 131 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ import (
55
"fmt"
66
toxiproxyapi "github.com/Shopify/toxiproxy/v2/client"
77
. "github.com/onsi/gomega"
8-
"math/rand"
8+
corev1 "k8s.io/api/core/v1"
9+
"net"
910
"os"
1011
"path"
1112
"regexp"
1213
"sigs.k8s.io/cluster-api/test/framework"
14+
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
1315
"sigs.k8s.io/cluster-api/test/framework/exec"
16+
"sigs.k8s.io/controller-runtime/pkg/client"
1417
"strconv"
1518
"strings"
1619
)
@@ -40,11 +43,13 @@ func ToxiProxyServerKill(ctx context.Context) error {
4043

4144
type ToxiProxyContext struct {
4245
KubeconfigPath string
46+
Secret corev1.Secret
4347
ClusterProxy framework.ClusterProxy
4448
ToxiProxy *toxiproxyapi.Proxy
49+
ConfigPath string
4550
}
4651

47-
func SetupForToxiProxyTesting(bootstrapClusterProxy framework.ClusterProxy) *ToxiProxyContext {
52+
func SetupForToxiProxyTestingBootstrapCluster(bootstrapClusterProxy framework.ClusterProxy, clusterName string) *ToxiProxyContext {
4853
// Read/parse the actual kubeconfig for the cluster
4954
kubeConfig := NewKubeconfig()
5055
unproxiedKubeconfigPath := bootstrapClusterProxy.GetKubeconfigPath()
@@ -56,21 +61,14 @@ func SetupForToxiProxyTesting(bootstrapClusterProxy framework.ClusterProxy) *Tox
5661
Expect(err).To(BeNil())
5762

5863
// Decompose server url into protocol, address and port
59-
serverRegex := regexp.MustCompilePOSIX("(https?)://([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+):([0-9]*)")
60-
urlComponents := serverRegex.FindStringSubmatch(server)
61-
Expect(len(urlComponents)).To(Equal(4))
62-
protocol := urlComponents[1]
63-
address := urlComponents[2]
64-
port, err := strconv.Atoi(urlComponents[3])
65-
Expect(err).To(BeNil())
64+
protocol, address, port, _ := parseUrl(server)
6665

6766
// Format into the needed addresses/URL form
6867
actualBootstrapClusterAddress := fmt.Sprintf("%v:%v", address, port)
6968

7069
// Create the toxiProxy for this test
7170
toxiProxyClient := toxiproxyapi.NewClient("127.0.0.1:8474")
72-
randomTestId := rand.Intn(65535)
73-
toxiProxyName := fmt.Sprintf("deploy_app_toxi_test_%#x", randomTestId)
71+
toxiProxyName := fmt.Sprintf("deploy_app_toxi_test_%v_bootstrap", clusterName)
7472
proxy, err := toxiProxyClient.CreateProxy(toxiProxyName, "127.0.0.1:0", actualBootstrapClusterAddress)
7573
Expect(err).To(BeNil())
7674

@@ -84,7 +82,7 @@ func SetupForToxiProxyTesting(bootstrapClusterProxy framework.ClusterProxy) *Tox
8482
// Write the modified kubeconfig using a new name.
8583
extension := path.Ext(unproxiedKubeconfigPath)
8684
baseWithoutExtension := strings.TrimSuffix(path.Base(unproxiedKubeconfigPath), extension)
87-
toxiProxyKubeconfigFileName := fmt.Sprintf("toxiProxy_%v_%#x%v", baseWithoutExtension, randomTestId, extension)
85+
toxiProxyKubeconfigFileName := fmt.Sprintf("toxiProxy_%v_%v%v", baseWithoutExtension, clusterName, extension)
8886
toxiProxyKubeconfigPath := path.Join("/tmp", toxiProxyKubeconfigFileName)
8987
err = kubeConfig.Save(toxiProxyKubeconfigPath)
9088
Expect(err).To(BeNil())
@@ -104,7 +102,7 @@ func SetupForToxiProxyTesting(bootstrapClusterProxy framework.ClusterProxy) *Tox
104102
}
105103
}
106104

107-
func TearDownToxiProxy(toxiProxyContext *ToxiProxyContext) {
105+
func TearDownToxiProxyBootstrap(toxiProxyContext *ToxiProxyContext) {
108106
// Tear down the proxy
109107
err := toxiProxyContext.ToxiProxy.Delete()
110108
Expect(err).To(BeNil())
@@ -134,3 +132,123 @@ func (tp *ToxiProxyContext) AddLatencyToxic(latencyMs int, jitterMs int, toxicit
134132

135133
return toxicName
136134
}
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)