feat: Load tests

This commit is contained in:
TheiLLeniumStudios
2026-01-06 11:03:26 +01:00
parent a5d1012570
commit 9a3edf13d2
17 changed files with 6191 additions and 82 deletions
+29 -5
View File
@@ -1,6 +1,8 @@
package handler
import (
"time"
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
@@ -11,23 +13,45 @@ import (
// ResourceCreatedHandler contains new objects
type ResourceCreatedHandler struct {
Resource interface{}
Collectors metrics.Collectors
Recorder record.EventRecorder
Resource interface{}
Collectors metrics.Collectors
Recorder record.EventRecorder
EnqueueTime time.Time // Time when this handler was added to the queue
}
// GetEnqueueTime returns when this handler was enqueued
func (r ResourceCreatedHandler) GetEnqueueTime() time.Time {
return r.EnqueueTime
}
// Handle processes the newly created resource
func (r ResourceCreatedHandler) Handle() error {
startTime := time.Now()
result := "success"
defer func() {
r.Collectors.RecordReconcile(result, time.Since(startTime))
}()
if r.Resource == nil {
logrus.Errorf("Resource creation handler received nil resource")
result = "error"
} else {
config, _ := r.GetConfig()
// Send webhook
if options.WebhookUrl != "" {
return sendUpgradeWebhook(config, options.WebhookUrl)
err := sendUpgradeWebhook(config, options.WebhookUrl)
if err != nil {
result = "error"
}
return err
}
// process resource based on its type
return doRollingUpgrade(config, r.Collectors, r.Recorder, invokeReloadStrategy)
err := doRollingUpgrade(config, r.Collectors, r.Recorder, invokeReloadStrategy)
if err != nil {
result = "error"
}
return err
}
return nil
}
+28 -5
View File
@@ -3,6 +3,7 @@ package handler
import (
"fmt"
"slices"
"time"
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/callbacks"
@@ -20,23 +21,45 @@ import (
// ResourceDeleteHandler contains new objects
type ResourceDeleteHandler struct {
Resource interface{}
Collectors metrics.Collectors
Recorder record.EventRecorder
Resource interface{}
Collectors metrics.Collectors
Recorder record.EventRecorder
EnqueueTime time.Time // Time when this handler was added to the queue
}
// GetEnqueueTime returns when this handler was enqueued
func (r ResourceDeleteHandler) GetEnqueueTime() time.Time {
return r.EnqueueTime
}
// Handle processes resources being deleted
func (r ResourceDeleteHandler) Handle() error {
startTime := time.Now()
result := "success"
defer func() {
r.Collectors.RecordReconcile(result, time.Since(startTime))
}()
if r.Resource == nil {
logrus.Errorf("Resource delete handler received nil resource")
result = "error"
} else {
config, _ := r.GetConfig()
// Send webhook
if options.WebhookUrl != "" {
return sendUpgradeWebhook(config, options.WebhookUrl)
err := sendUpgradeWebhook(config, options.WebhookUrl)
if err != nil {
result = "error"
}
return err
}
// process resource based on its type
return doRollingUpgrade(config, r.Collectors, r.Recorder, invokeDeleteStrategy)
err := doRollingUpgrade(config, r.Collectors, r.Recorder, invokeDeleteStrategy)
if err != nil {
result = "error"
}
return err
}
return nil
}
+10 -1
View File
@@ -1,9 +1,18 @@
package handler
import "github.com/stakater/Reloader/pkg/common"
import (
"time"
"github.com/stakater/Reloader/pkg/common"
)
// ResourceHandler handles the creation and update of resources
type ResourceHandler interface {
Handle() error
GetConfig() (common.Config, string)
}
// TimedHandler is a handler that tracks when it was enqueued
type TimedHandler interface {
GetEnqueueTime() time.Time
}
+30 -2
View File
@@ -1,6 +1,8 @@
package handler
import (
"time"
"github.com/sirupsen/logrus"
"github.com/stakater/Reloader/internal/pkg/metrics"
"github.com/stakater/Reloader/internal/pkg/options"
@@ -16,21 +18,47 @@ type ResourceUpdatedHandler struct {
OldResource interface{}
Collectors metrics.Collectors
Recorder record.EventRecorder
EnqueueTime time.Time // Time when this handler was added to the queue
}
// GetEnqueueTime returns when this handler was enqueued
func (r ResourceUpdatedHandler) GetEnqueueTime() time.Time {
return r.EnqueueTime
}
// Handle processes the updated resource
func (r ResourceUpdatedHandler) Handle() error {
startTime := time.Now()
result := "success"
defer func() {
r.Collectors.RecordReconcile(result, time.Since(startTime))
}()
if r.Resource == nil || r.OldResource == nil {
logrus.Errorf("Resource update handler received nil resource")
result = "error"
} else {
config, oldSHAData := r.GetConfig()
if config.SHAValue != oldSHAData {
// Send a webhook if update
if options.WebhookUrl != "" {
return sendUpgradeWebhook(config, options.WebhookUrl)
err := sendUpgradeWebhook(config, options.WebhookUrl)
if err != nil {
result = "error"
}
return err
}
// process resource based on its type
return doRollingUpgrade(config, r.Collectors, r.Recorder, invokeReloadStrategy)
err := doRollingUpgrade(config, r.Collectors, r.Recorder, invokeReloadStrategy)
if err != nil {
result = "error"
}
return err
} else {
// No data change - skip
result = "skipped"
r.Collectors.RecordSkipped("no_data_change")
}
}
return nil
+31 -12
View File
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"time"
"github.com/parnurzeal/gorequest"
"github.com/prometheus/client_golang/prometheus"
@@ -236,23 +237,34 @@ func rollingUpgrade(clients kube.Clients, config common.Config, upgradeFuncs cal
func PerformAction(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy) error {
items := upgradeFuncs.ItemsFunc(clients, config.Namespace)
// Record workloads scanned
collectors.RecordWorkloadsScanned(upgradeFuncs.ResourceType, len(items))
matchedCount := 0
for _, item := range items {
err := retryOnConflict(retry.DefaultRetry, func(fetchResource bool) error {
return upgradeResource(clients, config, upgradeFuncs, collectors, recorder, strategy, item, fetchResource)
err := retryOnConflict(retry.DefaultRetry, func(fetchResource bool) (bool, error) {
matched, err := upgradeResource(clients, config, upgradeFuncs, collectors, recorder, strategy, item, fetchResource)
if matched {
matchedCount++
}
return matched, err
})
if err != nil {
return err
}
}
// Record workloads matched
collectors.RecordWorkloadsMatched(upgradeFuncs.ResourceType, matchedCount)
return nil
}
func retryOnConflict(backoff wait.Backoff, fn func(_ bool) error) error {
func retryOnConflict(backoff wait.Backoff, fn func(_ bool) (bool, error)) error {
var lastError error
fetchResource := false // do not fetch resource on first attempt, already done by ItemsFunc
err := wait.ExponentialBackoff(backoff, func() (bool, error) {
err := fn(fetchResource)
_, err := fn(fetchResource)
fetchResource = true
switch {
case err == nil:
@@ -270,17 +282,19 @@ func retryOnConflict(backoff wait.Backoff, fn func(_ bool) error) error {
return err
}
func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy, resource runtime.Object, fetchResource bool) error {
func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs callbacks.RollingUpgradeFuncs, collectors metrics.Collectors, recorder record.EventRecorder, strategy invokeStrategy, resource runtime.Object, fetchResource bool) (bool, error) {
actionStartTime := time.Now()
accessor, err := meta.Accessor(resource)
if err != nil {
return err
return false, err
}
resourceName := accessor.GetName()
if fetchResource {
resource, err = upgradeFuncs.ItemFunc(clients, resourceName, config.Namespace)
if err != nil {
return err
return false, err
}
}
annotations := upgradeFuncs.AnnotationsFunc(resource)
@@ -289,13 +303,14 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
if !result.ShouldReload {
logrus.Debugf("No changes detected in '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace)
return nil
return false, nil
}
strategyResult := strategy(upgradeFuncs, resource, config, result.AutoReload)
if strategyResult.Result != constants.Updated {
return nil
collectors.RecordSkipped("strategy_not_updated")
return false, nil
}
// find correct annotation and update the resource
@@ -309,7 +324,7 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
_, err = PauseDeployment(deployment, clients, config.Namespace, pauseInterval)
if err != nil {
logrus.Errorf("Failed to pause deployment '%s' in namespace '%s': %v", resourceName, config.Namespace, err)
return err
return true, err
}
}
}
@@ -320,16 +335,19 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
err = upgradeFuncs.UpdateFunc(clients, config.Namespace, resource)
}
actionLatency := time.Since(actionStartTime)
if err != nil {
message := fmt.Sprintf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
logrus.Errorf("Update for '%s' of type '%s' in namespace '%s' failed with error %v", resourceName, upgradeFuncs.ResourceType, config.Namespace, err)
collectors.Reloaded.With(prometheus.Labels{"success": "false"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "false", "namespace": config.Namespace}).Inc()
collectors.RecordAction(upgradeFuncs.ResourceType, "error", actionLatency)
if recorder != nil {
recorder.Event(resource, v1.EventTypeWarning, "ReloadFail", message)
}
return err
return true, err
} else {
message := fmt.Sprintf("Changes detected in '%s' of type '%s' in namespace '%s'", config.ResourceName, config.Type, config.Namespace)
message += fmt.Sprintf(", Updated '%s' of type '%s' in namespace '%s'", resourceName, upgradeFuncs.ResourceType, config.Namespace)
@@ -338,6 +356,7 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
collectors.Reloaded.With(prometheus.Labels{"success": "true"}).Inc()
collectors.ReloadedByNamespace.With(prometheus.Labels{"success": "true", "namespace": config.Namespace}).Inc()
collectors.RecordAction(upgradeFuncs.ResourceType, "success", actionLatency)
alert_on_reload, ok := os.LookupEnv("ALERT_ON_RELOAD")
if recorder != nil {
recorder.Event(resource, v1.EventTypeNormal, "Reloaded", message)
@@ -350,7 +369,7 @@ func upgradeResource(clients kube.Clients, config common.Config, upgradeFuncs ca
}
}
return nil
return true, nil
}
func getVolumeMountName(volumes []v1.Volume, mountType string, volumeName string) string {