mirror of
https://github.com/stakater/Reloader.git
synced 2026-02-14 18:09:50 +00:00
392 lines
10 KiB
Go
392 lines
10 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stakater/Reloader/internal/pkg/options"
|
|
"github.com/stakater/Reloader/pkg/kube"
|
|
"github.com/stretchr/testify/assert"
|
|
appsv1 "k8s.io/api/apps/v1"
|
|
"k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
testclient "k8s.io/client-go/kubernetes/fake"
|
|
)
|
|
|
|
func TestIsPaused(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deployment *appsv1.Deployment
|
|
paused bool
|
|
}{
|
|
{
|
|
name: "paused deployment",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
},
|
|
paused: true,
|
|
},
|
|
{
|
|
name: "unpaused deployment",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: false,
|
|
},
|
|
},
|
|
paused: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := IsPaused(test.deployment)
|
|
assert.Equal(t, test.paused, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsPausedByReloader(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deployment *appsv1.Deployment
|
|
pausedByReloader bool
|
|
}{
|
|
{
|
|
name: "paused by reloader",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentTimeAnnotation: time.Now().Format(time.RFC3339),
|
|
},
|
|
},
|
|
},
|
|
pausedByReloader: true,
|
|
},
|
|
{
|
|
name: "not paused by reloader",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{},
|
|
},
|
|
},
|
|
pausedByReloader: false,
|
|
},
|
|
{
|
|
name: "not paused",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: false,
|
|
},
|
|
},
|
|
pausedByReloader: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
pausedByReloader := IsPausedByReloader(test.deployment)
|
|
assert.Equal(t, test.pausedByReloader, pausedByReloader)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetPauseStartTime(t *testing.T) {
|
|
now := time.Now()
|
|
nowStr := now.Format(time.RFC3339)
|
|
|
|
tests := []struct {
|
|
name string
|
|
deployment *appsv1.Deployment
|
|
pausedByReloader bool
|
|
expectedStartTime time.Time
|
|
}{
|
|
{
|
|
name: "valid pause time",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentTimeAnnotation: nowStr,
|
|
},
|
|
},
|
|
},
|
|
pausedByReloader: true,
|
|
expectedStartTime: now,
|
|
},
|
|
{
|
|
name: "not paused by reloader",
|
|
deployment: &appsv1.Deployment{
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: false,
|
|
},
|
|
},
|
|
pausedByReloader: false,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
actualStartTime, err := GetPauseStartTime(test.deployment)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if !test.pausedByReloader {
|
|
assert.Nil(t, actualStartTime)
|
|
} else {
|
|
assert.NotNil(t, actualStartTime)
|
|
assert.WithinDuration(t, test.expectedStartTime, *actualStartTime, time.Second)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePauseDuration(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pauseIntervalValue string
|
|
expectedDuration time.Duration
|
|
invalidDuration bool
|
|
}{
|
|
{
|
|
name: "valid duration",
|
|
pauseIntervalValue: "10s",
|
|
expectedDuration: 10 * time.Second,
|
|
invalidDuration: false,
|
|
},
|
|
{
|
|
name: "valid minute duration",
|
|
pauseIntervalValue: "2m",
|
|
expectedDuration: 2 * time.Minute,
|
|
invalidDuration: false,
|
|
},
|
|
{
|
|
name: "invalid duration",
|
|
pauseIntervalValue: "invalid",
|
|
expectedDuration: 0,
|
|
invalidDuration: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
actualDuration, err := ParsePauseDuration(test.pauseIntervalValue)
|
|
|
|
if test.invalidDuration {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, test.expectedDuration, actualDuration)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleMissingTimerSimple(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deployment *appsv1.Deployment
|
|
shouldBePaused bool // Should be unpaused after HandleMissingTimer ?
|
|
}{
|
|
{
|
|
name: "deployment paused by reloader, pause period has expired and no timer",
|
|
deployment: &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-deployment-1",
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentTimeAnnotation: time.Now().Add(-6 * time.Minute).Format(time.RFC3339),
|
|
options.PauseDeploymentAnnotation: "5m",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
},
|
|
shouldBePaused: false,
|
|
},
|
|
{
|
|
name: "deployment paused by reloader, pause period expires in the future and no timer",
|
|
deployment: &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-deployment-2",
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentTimeAnnotation: time.Now().Add(1 * time.Minute).Format(time.RFC3339),
|
|
options.PauseDeploymentAnnotation: "5m",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
},
|
|
shouldBePaused: true,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Clean up any timers at the end of the test
|
|
defer func() {
|
|
for key, timer := range activeTimers {
|
|
timer.Stop()
|
|
delete(activeTimers, key)
|
|
}
|
|
}()
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fakeClient := testclient.NewClientset()
|
|
clients := kube.Clients{
|
|
KubernetesClient: fakeClient,
|
|
}
|
|
|
|
_, err := fakeClient.AppsV1().Deployments("default").Create(
|
|
context.TODO(),
|
|
test.deployment,
|
|
metav1.CreateOptions{})
|
|
assert.NoError(t, err, "Expected no error when creating deployment")
|
|
|
|
pauseDuration, _ := ParsePauseDuration(test.deployment.Annotations[options.PauseDeploymentAnnotation])
|
|
HandleMissingTimer(test.deployment, pauseDuration, clients, "default")
|
|
|
|
updatedDeployment, _ := fakeClient.AppsV1().Deployments("default").Get(context.TODO(), test.deployment.Name, metav1.GetOptions{})
|
|
|
|
assert.Equal(t, test.shouldBePaused, updatedDeployment.Spec.Paused,
|
|
"Deployment should have correct paused state after timer expiration")
|
|
|
|
if test.shouldBePaused {
|
|
pausedAtAnnotationValue := updatedDeployment.Annotations[options.PauseDeploymentTimeAnnotation]
|
|
assert.NotEmpty(t, pausedAtAnnotationValue,
|
|
"Pause annotation should be present and contain a value when deployment is paused")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPauseDeployment(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
deployment *appsv1.Deployment
|
|
expectedError bool
|
|
expectedPaused bool
|
|
expectedAnnotation bool // Should have pause time annotation
|
|
pauseInterval string
|
|
}{
|
|
{
|
|
name: "deployment without pause annotation",
|
|
deployment: &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-deployment",
|
|
Annotations: map[string]string{},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: false,
|
|
},
|
|
},
|
|
expectedError: true,
|
|
expectedPaused: false,
|
|
expectedAnnotation: false,
|
|
pauseInterval: "",
|
|
},
|
|
{
|
|
name: "deployment already paused but not by reloader",
|
|
deployment: &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-deployment",
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentAnnotation: "5m",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: true,
|
|
},
|
|
},
|
|
expectedError: false,
|
|
expectedPaused: true,
|
|
expectedAnnotation: false,
|
|
pauseInterval: "5m",
|
|
},
|
|
{
|
|
name: "deployment unpaused that needs to be paused by reloader",
|
|
deployment: &appsv1.Deployment{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "test-deployment-3",
|
|
Annotations: map[string]string{
|
|
options.PauseDeploymentAnnotation: "5m",
|
|
},
|
|
},
|
|
Spec: appsv1.DeploymentSpec{
|
|
Paused: false,
|
|
},
|
|
},
|
|
expectedError: false,
|
|
expectedPaused: true,
|
|
expectedAnnotation: true,
|
|
pauseInterval: "5m",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fakeClient := testclient.NewClientset()
|
|
clients := kube.Clients{
|
|
KubernetesClient: fakeClient,
|
|
}
|
|
|
|
_, err := fakeClient.AppsV1().Deployments("default").Create(
|
|
context.TODO(),
|
|
test.deployment,
|
|
metav1.CreateOptions{})
|
|
assert.NoError(t, err, "Expected no error when creating deployment")
|
|
|
|
updatedDeployment, err := PauseDeployment(test.deployment, clients, "default", test.pauseInterval)
|
|
if test.expectedError {
|
|
assert.Error(t, err, "Expected an error pausing the deployment")
|
|
return
|
|
} else {
|
|
assert.NoError(t, err, "Expected no error pausing the deployment")
|
|
}
|
|
|
|
assert.Equal(t, test.expectedPaused, updatedDeployment.Spec.Paused,
|
|
"Deployment should have correct paused state after pause")
|
|
|
|
if test.expectedAnnotation {
|
|
pausedAtAnnotationValue := updatedDeployment.Annotations[options.PauseDeploymentTimeAnnotation]
|
|
assert.NotEmpty(t, pausedAtAnnotationValue,
|
|
"Pause annotation should be present and contain a value when deployment is paused")
|
|
} else {
|
|
pausedAtAnnotationValue := updatedDeployment.Annotations[options.PauseDeploymentTimeAnnotation]
|
|
assert.Empty(t, pausedAtAnnotationValue,
|
|
"Pause annotation should not be present when deployment has not been paused by reloader")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Simple helper function for test cases
|
|
func FindDeploymentByName(deployments []runtime.Object, deploymentName string) (*appsv1.Deployment, error) {
|
|
for _, deployment := range deployments {
|
|
accessor, err := meta.Accessor(deployment)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error getting accessor for item: %v", err)
|
|
}
|
|
if accessor.GetName() == deploymentName {
|
|
deploymentObj, ok := deployment.(*appsv1.Deployment)
|
|
if !ok {
|
|
return nil, fmt.Errorf("failed to cast to Deployment")
|
|
}
|
|
return deploymentObj, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("deployment '%s' not found", deploymentName)
|
|
}
|