Skip to content

Commit

Permalink
test: Retina e2e scale test (#720)
Browse files Browse the repository at this point in the history
# Description

Create Retina E2E Scale tests using test/e2e framework.

## Related Issue

If this pull request is related to any issue, please mention it here.
Additionally, make sure that the issue is assigned to you before
submitting this pull request.

## Checklist

- [ ] I have read the [contributing
documentation](https://retina.sh/docs/contributing).
- [ ] I signed and signed-off the commits (`git commit -S -s ...`). See
[this
documentation](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)
on signing commits.
- [ ] I have correctly attributed the author(s) of the code.
- [ ] I have tested the changes locally.
- [ ] I have followed the project's style guidelines.
- [ ] I have updated the documentation, if necessary.
- [ ] I have added tests, if applicable.

## Screenshots (if applicable) or Testing Completed

Please add any relevant screenshots or GIFs to showcase the changes
made.

## Additional Notes

Add any additional notes or context about the pull request here.

---

Please refer to the [CONTRIBUTING.md](../CONTRIBUTING.md) file for more
information on how to contribute to this project.

---------

Signed-off-by: Alex Castilio dos Santos <[email protected]>
  • Loading branch information
alexcastilio authored Nov 12, 2024
1 parent 4f3dcb5 commit 85fb0b9
Show file tree
Hide file tree
Showing 22 changed files with 1,213 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,4 @@ jobs:
shell: bash
run: |
set -euo pipefail
go test -v ./test/e2e/*.go -timeout 30m -tags=e2e -count=1 -args -image-tag=$(make version) -image-registry=${{vars.ACR_NAME}} -image-namespace=${{github.repository}}
go test -v ./test/e2e/... -timeout 30m -tags=e2e -count=1 -args -image-tag=$(make version) -image-registry=${{vars.ACR_NAME}} -image-namespace=${{github.repository}}
24 changes: 24 additions & 0 deletions test/e2e/framework/helpers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package helpers

import (
"context"
"testing"
"time"
)

const safetyTimeout = 24 * time.Hour

// Context returns a context with a deadline set to the test deadline - 1 min to ensure cleanup.
// If the test deadline is not set, a deadline is set to Now + 24h to prevent the test from running indefinitely
func Context(t *testing.T) (context.Context, context.CancelFunc) {
deadline, ok := t.Deadline()
if !ok {
t.Log("Test deadline disabled, deadline set to Now + 24h to prevent test from running indefinitely")
deadline = time.Now().Add(safetyTimeout)
}

// Subtract a minute from the deadline to ensure we have time to cleanup
deadline = deadline.Add(-time.Minute)

return context.WithDeadline(context.Background(), deadline)
}
40 changes: 40 additions & 0 deletions test/e2e/framework/kubernetes/check-pod-status.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

const (
Expand All @@ -19,6 +20,45 @@ const (
printInterval = 5 // print to stdout every 5 iterations
)

type WaitPodsReady struct {
KubeConfigFilePath string
Namespace string
LabelSelector string
}

// Useful when wanting to do parameter checking, for example
// if a parameter length is known to be required less than 80 characters,
// do this here so we don't find out later on when we run the step
// when possible, try to avoid making external calls, this should be fast and simple
func (w *WaitPodsReady) Prevalidate() error {
return nil
}

// Primary step where test logic is executed
// Returning an error will cause the test to fail
func (w *WaitPodsReady) Run() error {

config, err := clientcmd.BuildConfigFromFlags("", w.KubeConfigFilePath)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("error creating Kubernetes client: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
defer cancel()

return WaitForPodReady(ctx, clientset, w.Namespace, w.LabelSelector)
}

// Require for background steps
func (w *WaitPodsReady) Stop() error {
return nil
}

func WaitForPodReady(ctx context.Context, clientset *kubernetes.Clientset, namespace, labelSelector string) error {
podReadyMap := make(map[string]bool)

Expand Down
44 changes: 37 additions & 7 deletions test/e2e/framework/kubernetes/create-namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package kubernetes
import (
"context"
"fmt"
"time"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
Expand All @@ -11,7 +12,36 @@ import (
"k8s.io/client-go/tools/clientcmd"
)

func CreateNamespace(kubeconfigpath, namespace string) error {
type CreateNamespace struct {
Namespace string
KubeConfigFilePath string
}

func (c *CreateNamespace) Run() error {
return CreateNamespaceFn(c.KubeConfigFilePath, c.Namespace)
}

func (c *CreateNamespace) Stop() error {
return nil
}

func (c *CreateNamespace) Prevalidate() error {
return nil
}

func (c *CreateNamespace) getNamespace() *v1.Namespace {
return &v1.Namespace{
TypeMeta: metav1.TypeMeta{
Kind: "Namespace",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: c.Namespace,
},
}
}

func CreateNamespaceFn(kubeconfigpath, namespace string) error {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfigpath)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
Expand All @@ -22,19 +52,19 @@ func CreateNamespace(kubeconfigpath, namespace string) error {
return fmt.Errorf("error creating Kubernetes client: %w", err)
}

ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
defer cancel()

_, err = clientset.CoreV1().Namespaces().Create(ctx, &v1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: namespace,
},
}, metav1.CreateOptions{})

if err != nil && !errors.IsAlreadyExists(err) {
return fmt.Errorf("error creating namespace: %w", err)
} else {
fmt.Printf("Namespace '%s' created successfully.\n", namespace)
return nil
return fmt.Errorf("failed to create namespace \"%s\": %w", namespace, err)
}

fmt.Printf("Namespace \"%s\" created.\n", namespace)

return nil
}
78 changes: 78 additions & 0 deletions test/e2e/framework/kubernetes/delete-namespace.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package kubernetes

import (
"context"
"fmt"
"log"
"time"

"k8s.io/apimachinery/pkg/api/errors"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
)

type DeleteNamespace struct {
Namespace string
KubeConfigFilePath string
}

func (d *DeleteNamespace) Run() error {
config, err := clientcmd.BuildConfigFromFlags("", d.KubeConfigFilePath)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("error creating Kubernetes client: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
defer cancel()

err = clientset.CoreV1().Namespaces().Delete(ctx, d.Namespace, metaV1.DeleteOptions{})
if err != nil {
if !errors.IsNotFound(err) {
return fmt.Errorf("failed to delete namespace \"%s\": %w", d.Namespace, err)
}
}

backoff := wait.Backoff{
Steps: 6,
Duration: 10 * time.Second,
Factor: 2.0,
// Jitter: 0.1,
}

// Check if namespace was deleted
return retry.OnError(backoff,
func(err error) bool {
log.Printf("%v. Checking again soon...", err)

return true
},
func() error {
_, err = clientset.CoreV1().Namespaces().Get(ctx, d.Namespace, metaV1.GetOptions{})
if errors.IsNotFound(err) {
return nil
}

if err == nil {
return fmt.Errorf("namespace \"%s\" still exists", d.Namespace)
}

return err
},
)
}

func (d *DeleteNamespace) Stop() error {
return nil
}

func (d *DeleteNamespace) Prevalidate() error {
return nil
}
2 changes: 1 addition & 1 deletion test/e2e/framework/kubernetes/install-retina-helm.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func (i *InstallHelmChart) Run() error {
}

// Creating extra namespace to deploy test pods
err = CreateNamespace(i.KubeConfigFilePath, common.TestPodNamespace)
err = CreateNamespaceFn(i.KubeConfigFilePath, common.TestPodNamespace)
if err != nil {
return fmt.Errorf("failed to create namespace %s: %w", i.Namespace, err)
}
Expand Down
87 changes: 87 additions & 0 deletions test/e2e/framework/scaletest/add-shared-labels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package scaletest

import (
"context"
"encoding/json"
"fmt"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

type patchStringValue struct {
Op string `json:"op"`
Path string `json:"path"`
Value string `json:"value"`
}

type AddSharedLabelsToAllPods struct {
KubeConfigFilePath string
NumSharedLabelsPerPod int
Namespace string
}

// Useful when wanting to do parameter checking, for example
// if a parameter length is known to be required less than 80 characters,
// do this here so we don't find out later on when we run the step
// when possible, try to avoid making external calls, this should be fast and simple
func (a *AddSharedLabelsToAllPods) Prevalidate() error {
return nil
}

// Primary step where test logic is executed
// Returning an error will cause the test to fail
func (a *AddSharedLabelsToAllPods) Run() error {

if a.NumSharedLabelsPerPod < 1 {
return nil
}

config, err := clientcmd.BuildConfigFromFlags("", a.KubeConfigFilePath)
if err != nil {
return fmt.Errorf("error building kubeconfig: %w", err)
}

clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return fmt.Errorf("error creating Kubernetes client: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultTimeoutSeconds*time.Second)
defer cancel()

resources, err := clientset.CoreV1().Pods(a.Namespace).List(ctx, metav1.ListOptions{})

patch := []patchStringValue{}

for i := 0; i < a.NumSharedLabelsPerPod; i++ {
patch = append(patch, patchStringValue{
Op: "add",
Path: "/metadata/labels/shared-lab-" + fmt.Sprintf("%05d", i),
Value: "val",
})
}

patchBytes, err := json.Marshal(patch)
if err != nil {
return fmt.Errorf("error marshalling patch: %w", err)
}

for _, resource := range resources.Items {
clientset.CoreV1().Pods(a.Namespace).Patch(ctx, resource.Name,
types.JSONPatchType,
patchBytes,
metav1.PatchOptions{},
)
}

return nil
}

// Require for background steps
func (a *AddSharedLabelsToAllPods) Stop() error {
return nil
}
Loading

0 comments on commit 85fb0b9

Please sign in to comment.