Skip to content

Commit caa0831

Browse files
committed
Add dataplane performance automated test
1 parent b77d74b commit caa0831

22 files changed

+550
-109
lines changed

tests/Makefile

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ NGINX_PREFIX = $(PREFIX)/nginx
44
PULL_POLICY=Never
55
GW_API_VERSION ?= 1.0.0
66
K8S_VERSION ?= latest ## Expected format: 1.24 (major.minor) or latest
7+
GW_SERVICE_TYPE=NodePort
8+
GW_SVC_GKE_INTERNAL=false
79

810
.PHONY: help
911
help: Makefile ## Display this help
@@ -24,8 +26,20 @@ load-images: ## Load NGF and NGINX images on configured kind cluster
2426
test: ## Run the system tests against your default k8s cluster
2527
go test -v ./suite -args --gateway-api-version=$(GW_API_VERSION) --image-tag=$(TAG) \
2628
--ngf-image-repo=$(PREFIX) --nginx-image-repo=$(NGINX_PREFIX) --pull-policy=$(PULL_POLICY) \
27-
--k8s-version=$(K8S_VERSION)
29+
--k8s-version=$(K8S_VERSION) --service-type=$(GW_SERVICE_TYPE) --is-gke-internal-lb=$(GW_SVC_GKE_INTERNAL)
2830

2931
.PHONY: delete-kind-cluster
3032
delete-kind-cluster: ## Delete kind cluster
3133
kind delete cluster
34+
35+
.PHONY: reset-etc-hosts
36+
reset-etc-hosts: ## Reset the /etc/hosts file to delete the entry for cafe.example.com
37+
sudo sed -i -e '/cafe.example.com/d' /etc/hosts
38+
39+
.PHONY: run-tests-on-vm
40+
run-tests-on-vm: ## Run the tests on a GCP VM
41+
bash utils/run-tests-gcp-vm.sh
42+
43+
.PHONY: cleanup-vm
44+
cleanup-vm: ## Delete the test GCP VM
45+
bash utils/cleanup-vm.sh

tests/README.md

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,24 @@ build-images Build NGF and NGINX images
3333
create-kind-cluster Create a kind cluster
3434
delete-kind-cluster Delete kind cluster
3535
help Display this help
36+
install-gcp-deps Install dependencies on a GCP VM. To be ran only from a VM.
3637
load-images Load NGF and NGINX images on configured kind cluster
38+
reset-etc-hosts Reset the /etc/hosts file to delete the entry for cafe.example.com
3739
test Run the system tests against your default k8s cluster
3840
```
3941

4042
**Note:** The following variables are configurable when running the below `make` commands:
4143

42-
| Variable | Default | Description |
43-
|----------|---------|-------------|
44-
| TAG | edge | tag for the locally built NGF images |
45-
| PREFIX | nginx-gateway-fabric | prefix for the locally built NGF image |
46-
| NGINX_PREFIX | nginx-gateway-fabric/nginx | prefix for the locally built NGINX image |
47-
| PULL_POLICY | Never | NGF image pull policy |
48-
| GW_API_VERSION | 1.0.0 | version of Gateway API resources to install |
49-
| K8S_VERSION | latest | version of k8s that the tests are run on |
44+
| Variable | Default | Description |
45+
| ------------------- | -------------------------- | -------------------------------------------------------------- |
46+
| TAG | edge | tag for the locally built NGF images |
47+
| PREFIX | nginx-gateway-fabric | prefix for the locally built NGF image |
48+
| NGINX_PREFIX | nginx-gateway-fabric/nginx | prefix for the locally built NGINX image |
49+
| PULL_POLICY | Never | NGF image pull policy |
50+
| GW_API_VERSION | 1.0.0 | version of Gateway API resources to install |
51+
| K8S_VERSION | latest | version of k8s that the tests are run on |
52+
| GW_SERVICE_TYPE | NodePort | Type of Service that should be created |
53+
| GW_SVC_GKE_INTERNAL | false | Specifies if the LoadBalancer should be a GKE internal service |
5054

5155
## Step 1 - Create a Kubernetes cluster
5256

@@ -74,10 +78,28 @@ make build-images load-images TAG=$(whoami)
7478

7579
## Step 3 - Run the tests
7680

81+
### 3a - Run the tests locally
82+
83+
The tests require `sudo` access locally to create an entry in the `/etc/hosts` file.
84+
85+
```makefile
86+
sudo make test TAG=$(whoami)
87+
```
88+
89+
### 3b - Run the tests on a GKE cluster from a GCP VM
90+
91+
This step only applies if you would like to run the tests from a GCP based VM. The VM should be created in the same
92+
zone as your GKE cluster, and requires a service account that has Kubernetes admin permissions. Additionally, you need
93+
ssh access to the VM and the VM needs to have network access to the Kubernetes control node.
94+
95+
Before running the below `make` command, populate the required env vars in `utils/vars.env`.
96+
7797
```makefile
78-
make test TAG=$(whoami)
98+
make run-tests-on-vm
7999
```
80100

101+
### Common test amendments
102+
81103
To run a specific test, you can "focus" it by adding the `F` prefix to the name. For example:
82104

83105
```go
@@ -112,8 +134,22 @@ XIt("runs some test", func(){
112134
})
113135
```
114136

115-
## Step 4 - Delete kind cluster
137+
## Step 4 - Cleanup
116138

117-
```makefile
118-
make delete-kind-cluster
119-
```
139+
1. Delete kind cluster, if required
140+
141+
```makefile
142+
make delete-kind-cluster
143+
```
144+
145+
2. Remove entries from `/etc/hosts`, if required
146+
147+
```makefile
148+
make reset-etc-hosts
149+
```
150+
151+
3. Delete the cloud VM, if required
152+
153+
```makefile
154+
make cleanup-vm
155+
```

tests/dataplane-performance/setup.md

Lines changed: 0 additions & 80 deletions
This file was deleted.

tests/framework/ngf.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type InstallationConfig struct {
2929
ImageTag string
3030
ImagePullPolicy string
3131
ServiceType string
32+
IsGKEInternalLB bool
3233
}
3334

3435
// InstallGatewayAPI installs the specified version of the Gateway API resources.
@@ -110,6 +111,9 @@ func InstallNGF(cfg InstallationConfig, extraArgs ...string) ([]byte, error) {
110111

111112
if cfg.ServiceType != "" {
112113
args = append(args, formatValueSet("service.type", cfg.ServiceType)...)
114+
if cfg.ServiceType == "LoadBalancer" && cfg.IsGKEInternalLB {
115+
args = append(args, formatValueSet(`service.annotations.networking\.gke\.io\/load-balancer-type`, "Internal")...)
116+
}
113117
}
114118

115119
fullArgs := append(args, extraArgs...)

tests/framework/request.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import (
1111
)
1212

1313
// Get sends a GET request to the specified url.
14-
// It resolves to localhost (where the NGF port-forward is running) instead of using DNS.
14+
// It resolves to the specified address instead of using DNS.
1515
// The status and body of the response is returned, or an error.
16-
func Get(url string, timeout time.Duration) (int, string, error) {
16+
func Get(url string, address string, timeout time.Duration) (int, string, error) {
1717
dialer := &net.Dialer{}
1818

1919
http.DefaultTransport.(*http.Transport).DialContext = func(
@@ -23,7 +23,7 @@ func Get(url string, timeout time.Duration) (int, string, error) {
2323
) (net.Conn, error) {
2424
split := strings.Split(addr, ":")
2525
port := split[len(split)-1]
26-
return dialer.DialContext(ctx, network, fmt.Sprintf("127.0.0.1:%s", port))
26+
return dialer.DialContext(ctx, network, fmt.Sprintf("%s:%s", address, port))
2727
}
2828

2929
ctx, cancel := context.WithTimeout(context.Background(), timeout)

tests/framework/resourcemanager.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,18 @@ type ResourceManager struct {
4848
TimeoutConfig TimeoutConfig
4949
}
5050

51+
// ClusterInfo holds the cluster metadata
52+
type ClusterInfo struct {
53+
K8sVersion string
54+
MemoryPerNode string
55+
GkeInstanceType string
56+
GkeZone string
57+
NodeCount int
58+
CPUCountPerNode int64
59+
MaxPodsPerNode int64
60+
IsGKE bool
61+
}
62+
5163
// Apply creates or updates Kubernetes resources defined as Go objects.
5264
func (rm *ResourceManager) Apply(resources []client.Object) error {
5365
ctx, cancel := context.WithTimeout(context.Background(), rm.TimeoutConfig.CreateTimeout)
@@ -311,3 +323,86 @@ func (rm *ResourceManager) waitForRoutesToBeReady(ctx context.Context, namespace
311323
},
312324
)
313325
}
326+
327+
// GetLBIPAddress gets the IP or Hostname from the Loadbalancer service.
328+
func (rm *ResourceManager) GetLBIPAddress(namespace string) (string, error) {
329+
ctx, cancel := context.WithTimeout(context.Background(), rm.TimeoutConfig.CreateTimeout)
330+
defer cancel()
331+
var serviceList core.ServiceList
332+
var address string
333+
if err := rm.K8sClient.List(ctx, &serviceList, client.InNamespace(namespace)); err != nil {
334+
return "", err
335+
}
336+
var nsName types.NamespacedName
337+
338+
for _, svc := range serviceList.Items {
339+
if svc.Spec.Type == core.ServiceTypeLoadBalancer {
340+
nsName = types.NamespacedName{Namespace: svc.GetNamespace(), Name: svc.GetName()}
341+
if err := rm.waitForLBStatusToBeReady(ctx, nsName); err != nil {
342+
return "", fmt.Errorf("Error getting status from LoadBalancer service: %w", err)
343+
}
344+
}
345+
}
346+
347+
if nsName.Name != "" {
348+
var lbService core.Service
349+
350+
if err := rm.K8sClient.Get(ctx, nsName, &lbService); err != nil {
351+
return "", fmt.Errorf("Error getting LoadBalancer service: %w", err)
352+
}
353+
if lbService.Status.LoadBalancer.Ingress[0].IP != "" {
354+
address = lbService.Status.LoadBalancer.Ingress[0].IP
355+
} else if lbService.Status.LoadBalancer.Ingress[0].Hostname != "" {
356+
address = lbService.Status.LoadBalancer.Ingress[0].Hostname
357+
}
358+
return address, nil
359+
}
360+
return "", nil
361+
}
362+
363+
func (rm *ResourceManager) waitForLBStatusToBeReady(ctx context.Context, svcNsName types.NamespacedName) error {
364+
return wait.PollUntilContextCancel(
365+
ctx,
366+
500*time.Millisecond,
367+
true, /* poll immediately */
368+
func(ctx context.Context) (bool, error) {
369+
var svc core.Service
370+
if err := rm.K8sClient.Get(ctx, svcNsName, &svc); err != nil {
371+
return false, err
372+
}
373+
if len(svc.Status.LoadBalancer.Ingress) > 0 {
374+
return true, nil
375+
}
376+
377+
return false, nil
378+
},
379+
)
380+
}
381+
382+
// GetClusterInfo retrieves node info and Kubernetes version from the cluster
383+
func (rm *ResourceManager) GetClusterInfo() (ClusterInfo, error) {
384+
ctx, cancel := context.WithTimeout(context.Background(), rm.TimeoutConfig.GetTimeout)
385+
defer cancel()
386+
var nodes core.NodeList
387+
ci := &ClusterInfo{}
388+
if err := rm.K8sClient.List(ctx, &nodes); err != nil {
389+
return *ci, fmt.Errorf("error getting nodes: %w", err)
390+
}
391+
392+
ci.NodeCount = len(nodes.Items)
393+
394+
node := nodes.Items[0]
395+
ci.K8sVersion = node.Status.NodeInfo.KubeletVersion
396+
ci.CPUCountPerNode, _ = node.Status.Capacity.Cpu().AsInt64()
397+
ci.MemoryPerNode = node.Status.Capacity.Memory().String()
398+
ci.MaxPodsPerNode, _ = node.Status.Capacity.Pods().AsInt64()
399+
providerID := node.Spec.ProviderID
400+
401+
if strings.Split(providerID, "://")[0] == "gce" {
402+
ci.IsGKE = true
403+
ci.GkeInstanceType = node.Labels["beta.kubernetes.io/instance-type"]
404+
ci.GkeZone = node.Labels["topology.kubernetes.io/zone"]
405+
}
406+
407+
return *ci, nil
408+
}

0 commit comments

Comments
 (0)