Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automate scale test #1804

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go 1.22.0
replace github.com/chzyer/logex v1.1.10 => github.com/chzyer/logex v1.2.0

require (
cloud.google.com/go/monitoring v1.18.0
github.com/go-kit/log v0.2.1
github.com/go-logr/logr v1.4.1
github.com/google/go-cmp v0.6.0
Expand All @@ -23,6 +24,8 @@ require (
go.opentelemetry.io/otel v1.24.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.24.0
go.uber.org/zap v1.27.0
google.golang.org/api v0.162.0
google.golang.org/protobuf v1.33.0
k8s.io/api v0.29.3
k8s.io/apiextensions-apiserver v0.29.3
k8s.io/apimachinery v0.29.3
Expand All @@ -34,6 +37,8 @@ require (
)

require (
cloud.google.com/go/compute v1.23.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
Expand All @@ -57,7 +62,10 @@ require (
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
github.com/imdario/mergo v0.3.16 // indirect
Expand All @@ -79,12 +87,15 @@ require (
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect
github.com/stretchr/testify v1.8.4 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 // indirect
go.opentelemetry.io/otel/metric v1.24.0 // indirect
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
go.opentelemetry.io/otel/trace v1.24.0 // indirect
go.opentelemetry.io/proto/otlp v1.1.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
Expand All @@ -93,14 +104,14 @@ require (
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.19.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect
google.golang.org/grpc v1.61.1 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
106 changes: 98 additions & 8 deletions go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ stop-longevity-test: ## Stops the longevity test and collects results

.PHONY: .vm-nfr-test
.vm-nfr-test: ## Runs the NFR tests on the GCP VM (called by `nfr-test`)
go test -v ./suite -ginkgo.label-filter "nfr" $(GINKGO_FLAGS) -ginkgo.v -args --gateway-api-version=$(GW_API_VERSION) \
go test -v -timeout 1h ./suite -ginkgo.label-filter "nfr" $(GINKGO_FLAGS) -ginkgo.v -args --gateway-api-version=$(GW_API_VERSION) \
--gateway-api-prev-version=$(GW_API_PREV_VERSION) --image-tag=$(TAG) --version-under-test=$(NGF_VERSION) \
--plus-enabled=$(PLUS_ENABLED) --ngf-image-repo=$(PREFIX) --nginx-image-repo=$(NGINX_PREFIX) --nginx-plus-image-repo=$(NGINX_PLUS_PREFIX) \
--pull-policy=$(PULL_POLICY) --k8s-version=$(K8S_VERSION) --service-type=$(GW_SERVICE_TYPE) \
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
//go:build scale
// +build scale

package scale
package framework

import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
)

Expand Down Expand Up @@ -63,8 +61,7 @@
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQzZtTnJSdUZ2WXZoSE4KbXI3c1FvNUtKSUVDN3N6TFVrNExFeklSNS9yMEVaUjQ2RnRTaGJQd0ZuaXAwMFBxekhpVkhKYy92TjdkQTVLeApQS1VmdFJuQ1J6YldVaTZBZzJpRU93bXF6WUhGbVNpZkFlVjk0RlAxOGtSbjl1ckV3OEpiRXJIUncrVW51L25tCmFMRHF1eGpFTVBweGhuRklCSnYwK1R3djNEVGx6TjNwUlV6dnpidGZvZCtEVTZBSmR6N3Rid1dTNmR6MHc1Z2kKbW9RelZnbFpnVDBJek9FZkV3NVpWMnRMZllHZWRlRVJ1VjhtR041c09va3R2aGxsMU1udHRaMkZNVHgySmVjUQo3K0xBRm9YVnBTS2NjbUFVZ1JBM0xOOHdVZXBVTHZZdFhiUm1QTFc4SjFINmhFeHJHTHBiTERZNmpzbGxBNlZpCk0xMjVjU0hsQWdNQkFBRUNnZ0VBQnpaRE50bmVTdWxGdk9HZlFYaHRFWGFKdWZoSzJBenRVVVpEcUNlRUxvekQKWlV6dHdxbkNRNlJLczUyandWNTN4cU9kUU94bTNMbjNvSHdNa2NZcEliWW82MjJ2dUczYnkwaVEzaFlsVHVMVgpqQmZCcS9UUXFlL2NMdngvSkczQWhFNmJxdFRjZFlXeGFmTmY2eUtpR1dzZk11WVVXTWs4MGVJVUxuRmZaZ1pOCklYNTlSOHlqdE9CVm9Sa3hjYTVoMW1ZTDFsSlJNM3ZqVHNHTHFybmpOTjNBdWZ3ZGRpK1VDbGZVL2l0K1EvZkUKV216aFFoTlRpNVFkRWJLVStOTnYvNnYvb2JvandNb25HVVBCdEFTUE05cmxFemIralQ1WHdWQjgvLzRGY3VoSwoyVzNpcjhtNHVlQ1JHSVlrbGxlLzhuQmZ0eVhiVkNocVRyZFBlaGlPM1FLQmdRRGlrR3JTOTc3cjg3Y1JPOCtQClpoeXltNXo4NVIzTHVVbFNTazJiOTI1QlhvakpZL2RRZDVTdFVsSWE4OUZKZnNWc1JRcEhHaTFCYzBMaTY1YjIKazR0cE5xcVFoUmZ1UVh0UG9GYXRuQzlPRnJVTXJXbDVJN0ZFejZnNkNQMVBXMEg5d2hPemFKZUdpZVpNYjlYTQoybDdSSFZOcC9jTDlYbmhNMnN0Q1lua2Iwd0tCZ1FEUzF4K0crakEyUVNtRVFWNXA1RnRONGcyamsyZEFjMEhNClRIQ2tTazFDRjhkR0Z2UWtsWm5ZbUt0dXFYeXNtekJGcnZKdmt2eUhqbUNYYTducXlpajBEdDZtODViN3BGcVAKQWxtajdtbXI3Z1pUeG1ZMXBhRWFLMXY4SDNINGtRNVl3MWdrTWRybVJHcVAvaTBGaDVpaGtSZS9DOUtGTFVkSQpDcnJjTzhkUVp3S0JnSHA1MzRXVWNCMVZibzFlYStIMUxXWlFRUmxsTWlwRFM2TzBqeWZWSmtFb1BZSEJESnp2ClIrdzZLREJ4eFoyWmJsZ05LblV0YlhHSVFZd3lGelhNcFB5SGxNVHpiZkJhYmJLcDFyR2JVT2RCMXpXM09PRkgKcmppb21TUm1YNmxhaDk0SjRHU0lFZ0drNGw1SHhxZ3JGRDZ2UDd4NGRjUktJWFpLZ0w2dVJSSUpBb0dCQU1CVApaL2p5WStRNTBLdEtEZHUrYU9ORW4zaGxUN3hrNXRKN3NBek5rbWdGMU10RXlQUk9Xd1pQVGFJbWpRbk9qbHdpCldCZ2JGcXg0M2ZlQ1Z4ZXJ6V3ZEM0txaWJVbWpCTkNMTGtYeGh3ZEVteFQwVit2NzZGYzgwaTNNYVdSNnZZR08KditwVVovL0F6UXdJcWZ6dlVmV2ZxdStrMHlhVXhQOGNlcFBIRyt0bEFvR0FmQUtVVWhqeFU0Ym5vVzVwVUhKegpwWWZXZXZ5TW54NWZyT2VsSmRmNzlvNGMvMHhVSjh1eFBFWDFkRmNrZW96dHNpaVFTNkN6MENRY09XVWxtSkRwCnVrdERvVzM3VmNSQU1BVjY3NlgxQVZlM0UwNm5aL2g2Tkd4Z28rT042Q3pwL0lkMkJPUm9IMFAxa2RjY1NLT3kKMUtFZlNnb1B0c1N1eEpBZXdUZmxDMXc9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
`

var appTmplTxt = `apiVersion: v1
apiVersion: apps/v1
var appTmplTxt = `apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ . }}
Expand Down Expand Up @@ -118,11 +115,11 @@
BackendName string
}

func getPrereqDirName(manifestDir string) string {
return filepath.Join(manifestDir, "prereqs")
func GetPrereqDirName(manifestDir string) string {
return filepath.Join(manifestDir, "manifests/prereqs")
}

func generateScaleListenerManifests(numListeners int, manifestDir string, tls bool) error {
func GenerateScaleListenerManifests(numListeners int, manifestDir string, tls bool) error {
listeners := make([]Listener, 0)
backends := make([]string, 0)
secrets := make([]string, 0)
Expand Down Expand Up @@ -158,11 +155,11 @@
}
}

if err := generateSecrets(getPrereqDirName(manifestDir), secrets); err != nil {
if err := generateSecrets(GetPrereqDirName(manifestDir), secrets); err != nil {
return err
}

return generateBackendAppManifests(getPrereqDirName(manifestDir), backends)
return generateBackendAppManifests(GetPrereqDirName(manifestDir), backends)
}

func generateSecrets(secretsDir string, secrets []string) error {
Expand All @@ -180,7 +177,6 @@

path := filepath.Join(secretsDir, fmt.Sprintf("%s.yaml", secret))

fmt.Println("Writing", path)
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
return err
}
Expand All @@ -189,7 +185,7 @@
return nil
}

func generateScaleHTTPRouteManifests(numRoutes int, manifestDir string) error {
func GenerateScaleHTTPRouteManifests(numRoutes int, manifestDir string) error {
l := Listener{
Name: "listener",
HostnamePrefix: "*",
Expand Down Expand Up @@ -218,10 +214,12 @@

}

return generateBackendAppManifests(getPrereqDirName(manifestDir), []string{backendName})
return generateBackendAppManifests(GetPrereqDirName(manifestDir), []string{backendName})
}

func generateManifests(outDir string, version int, listeners []Listener, routes []Route) error {
manifestDir := filepath.Join(outDir, "manifests")

var buf bytes.Buffer

if len(listeners) > 0 {
Expand All @@ -240,15 +238,13 @@
}
}

err := os.Mkdir(outDir, 0o750)
if err != nil && !os.IsExist(err) {
if err := os.MkdirAll(manifestDir, 0o750); err != nil {
return err
}

filename := fmt.Sprintf("manifest-%d.yaml", version)
path := filepath.Join(outDir, filename)
path := filepath.Join(manifestDir, filename)

fmt.Println("Writing", path)
return os.WriteFile(path, buf.Bytes(), 0o600)
}

Expand All @@ -267,11 +263,27 @@

path := filepath.Join(outDir, fmt.Sprintf("%s.yaml", backend))

fmt.Println("Writing", path)
if err := os.WriteFile(path, buf.Bytes(), 0o600); err != nil {
return err
}
}

return nil
}

func GetYamlFileList(manifestDir string) ([]string, error) {
var yamlFiles []string
files, err := os.ReadDir(manifestDir)
if err != nil {
return yamlFiles, fmt.Errorf("error reading directory: %v", err)

Check failure on line 278 in tests/framework/generate_manifests.go

View workflow job for this annotation

GitHub Actions / Lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}

// Loop through each file
for _, file := range files {
// Check if it's a YAML file
if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
yamlFiles = append(yamlFiles, filepath.Join(manifestDir, file.Name()))
}
}
return yamlFiles, nil
}
145 changes: 145 additions & 0 deletions tests/framework/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package framework

import (
"context"
"encoding/csv"
"errors"
"fmt"
"strconv"
"strings"
"time"

monitoring "cloud.google.com/go/monitoring/apiv3/v2"
"cloud.google.com/go/monitoring/apiv3/v2/monitoringpb"
Comment on lines +12 to +13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per a conversation offline, I bet it would be easier to get metrics if we just deploy our own Prometheus, and not use the GCP monitoring. Then we also don't need this extra dependency.


"google.golang.org/api/iterator"
"google.golang.org/protobuf/types/known/timestamppb"
)

const (
CpuMetricName = "kubernetes.io/container/cpu/core_usage_time"

Check warning on line 20 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: const CpuMetricName should be CPUMetricName (revive)
MemoryMetricName = "kubernetes.io/container/memory/used_bytes"
PromReloads = "prometheus.googleapis.com/nginx_gateway_fabric_nginx_reloads_total/counter"
)

// NGFPrometheusMetrics are the relevant NGFPrometheusMetrics for scale tests
type NGFPrometheusMetrics struct {
ReloadCount string
ReloadErrsCount string
ReloadAvgTime string
ReloadsUnder500ms string
EventsCount string
EventsErrsCount string
EventsAvgTime string
EventsUnder500ms string
}

// WriteGKEMetricsData gathers the requested timeseries metrics data and writes it to the given CSV file.
func WriteGKEMetricsData(podName, metricType string, startTime, endTime int64, csvWriter *csv.Writer) error {
// Create a context and specify the desired project ID.
ctx := context.Background()
// projectID := os.Getenv("GKE_PROJECT")
// TODO: Remove hardcoded project ID
projectID := ""

// Create a MetricsClient for the Google Cloud Monitoring API.
metricsClient, err := monitoring.NewMetricClient(ctx)
if err != nil {
return fmt.Errorf("failed to create MetricsClient: %v", err)

Check failure on line 48 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}
defer metricsClient.Close()

// Create a filter string to retrieve the required metric for the specific pod.
metricValueName := strings.Split(metricType, "/")[3]
valueAggregate := fmt.Sprintf("value_aggregate: aggregate(value.%s)", metricValueName)
filter := fmt.Sprintf(`resource.type = "k8s_container" AND resource.label.pod_name = "%s" AND metric.type = "%s"`, podName, metricType)

Check failure on line 55 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

line is 136 characters (lll)
aggregation := &monitoringpb.Aggregation{
GroupByFields: []string{valueAggregate, "max(value_aggregate)"},
}

fmt.Println("Filter: ", filter)
// Prepare the request to retrieve time series data.
req := &monitoringpb.ListTimeSeriesRequest{
Name: "projects/" + projectID,
Filter: filter,
Aggregation: aggregation,
Interval: &monitoringpb.TimeInterval{
StartTime: &timestamppb.Timestamp{Seconds: startTime},
EndTime: &timestamppb.Timestamp{Seconds: endTime},
},
}

fmt.Println("start-time", startTime, "end-time", endTime)

dataFound, err := writeMetricsData(ctx, metricsClient, req, csvWriter)

Check failure on line 75 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

File is not `gofumpt`-ed (gofumpt)
if err != nil {
return err
}

if !dataFound {
// no data yet, wait and try again with new timestamp
time.Sleep(1 * time.Minute)
req.Interval.EndTime = &timestamppb.Timestamp{Seconds: time.Now().Unix()}
dataFound, err = writeMetricsData(ctx, metricsClient, req, csvWriter)
if err != nil {
return err
}
if !dataFound {
fmt.Println("Waited for 1 minute but still no data for time period, exiting...")
}
}
return nil
}

func writeMetricsData(ctx context.Context, metricsClient *monitoring.MetricClient, req *monitoringpb.ListTimeSeriesRequest, csvWriter *csv.Writer) (bool, error) {

Check failure on line 95 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

line is 162 characters (lll)
var dataFound bool
// Retrieve time series data.
it := metricsClient.ListTimeSeries(ctx, req)

for {
ts, err := it.Next()
if errors.Is(err, iterator.Done) {
fmt.Println("no more entries in iterator")
break
}
if err != nil {
return false, fmt.Errorf("failed to iterate time series data: %v", err)

Check failure on line 107 in tests/framework/metrics.go

View workflow job for this annotation

GitHub Actions / Lint

non-wrapping format verb for fmt.Errorf. Use `%w` to format errors (errorlint)
}
fmt.Println("Points in time series: ", len(ts.Points))
for _, point := range ts.Points {
timestamp := point.Interval.EndTime.Seconds
fmt.Println()
data := fmt.Sprintf("%.2f", point.GetValue().GetDoubleValue())
if data != "0.00" {
dataFound = true
err = csvWriter.Write([]string{strconv.FormatInt(timestamp, 10), data})
fmt.Println("Timestamp: ", strconv.FormatInt(timestamp, 10), timestamp)
if err != nil {
return false, fmt.Errorf("failed to write data to CSV file: %v", err)
}
} else {
fmt.Println("No data yet, try again...")
}
}
}
return dataFound, nil
}

//func getPrometheusMetricsData() (error, NGFPrometheusMetrics) {
// // Create a context and specify the desired project ID.
// ctx := context.Background()
// // projectID := os.Getenv("GKE_PROJECT")
// // TODO: Remove hardcoded project ID
// projectID := "f5-gcs-7899-ptg-ingrss-ctlr"
//
// var ngfPromMetrics NGFPrometheusMetrics
//
// // Create a MetricsClient for the Google Cloud Monitoring API.
// metricsClient, err := monitoring.NewMetricClient(ctx)
// if err != nil {
// return fmt.Errorf("failed to create MetricsClient: %v", err), ngfPromMetrics
// }
// defer metricsClient.Close()
//
//}
Loading
Loading