Create backoff mechanism for failed runners and allow re-creation of failed ephemeral runners (#4059)
This commit is contained in:
parent
d6e2790db5
commit
cae7efa2c6
|
|
@ -119,7 +119,7 @@ type EphemeralRunnerStatus struct {
|
||||||
RunnerJITConfig string `json:"runnerJITConfig,omitempty"`
|
RunnerJITConfig string `json:"runnerJITConfig,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
Failures map[string]bool `json:"failures,omitempty"`
|
Failures map[string]metav1.Time `json:"failures,omitempty"`
|
||||||
|
|
||||||
// +optional
|
// +optional
|
||||||
JobRequestId int64 `json:"jobRequestId,omitempty"`
|
JobRequestId int64 `json:"jobRequestId,omitempty"`
|
||||||
|
|
@ -137,6 +137,20 @@ type EphemeralRunnerStatus struct {
|
||||||
JobDisplayName string `json:"jobDisplayName,omitempty"`
|
JobDisplayName string `json:"jobDisplayName,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *EphemeralRunnerStatus) LastFailure() metav1.Time {
|
||||||
|
var maxTime metav1.Time
|
||||||
|
if len(s.Failures) == 0 {
|
||||||
|
return maxTime
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ts := range s.Failures {
|
||||||
|
if ts.After(maxTime.Time) {
|
||||||
|
maxTime = ts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxTime
|
||||||
|
}
|
||||||
|
|
||||||
// +kubebuilder:object:root=true
|
// +kubebuilder:object:root=true
|
||||||
|
|
||||||
// EphemeralRunnerList contains a list of EphemeralRunner
|
// EphemeralRunnerList contains a list of EphemeralRunner
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ package v1alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -459,9 +460,9 @@ func (in *EphemeralRunnerStatus) DeepCopyInto(out *EphemeralRunnerStatus) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.Failures != nil {
|
if in.Failures != nil {
|
||||||
in, out := &in.Failures, &out.Failures
|
in, out := &in.Failures, &out.Failures
|
||||||
*out = make(map[string]bool, len(*in))
|
*out = make(map[string]metav1.Time, len(*in))
|
||||||
for key, val := range *in {
|
for key, val := range *in {
|
||||||
(*out)[key] = val
|
(*out)[key] = *val.DeepCopy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7794,7 +7794,8 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
failures:
|
failures:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: boolean
|
format: date-time
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
jobDisplayName:
|
jobDisplayName:
|
||||||
type: string
|
type: string
|
||||||
|
|
|
||||||
|
|
@ -7794,7 +7794,8 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
failures:
|
failures:
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
type: boolean
|
format: date-time
|
||||||
|
type: string
|
||||||
type: object
|
type: object
|
||||||
jobDisplayName:
|
jobDisplayName:
|
||||||
type: string
|
type: string
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
ctrl "sigs.k8s.io/controller-runtime"
|
ctrl "sigs.k8s.io/controller-runtime"
|
||||||
|
|
@ -50,6 +51,19 @@ type EphemeralRunnerReconciler struct {
|
||||||
ResourceBuilder
|
ResourceBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// precompute backoff durations for failed ephemeral runners
|
||||||
|
// the len(failedRunnerBackoff) must be equal to maxFailures + 1
|
||||||
|
var failedRunnerBackoff = []time.Duration{
|
||||||
|
0,
|
||||||
|
5 * time.Second,
|
||||||
|
10 * time.Second,
|
||||||
|
20 * time.Second,
|
||||||
|
40 * time.Second,
|
||||||
|
80 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxFailures = 5
|
||||||
|
|
||||||
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners,verbs=get;list;watch;create;update;patch;delete
|
||||||
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/status,verbs=get;update;patch
|
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/status,verbs=get;update;patch
|
||||||
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/finalizers,verbs=get;list;watch;create;update;patch;delete
|
// +kubebuilder:rbac:groups=actions.github.com,resources=ephemeralrunners/finalizers,verbs=get;list;watch;create;update;patch;delete
|
||||||
|
|
@ -173,6 +187,29 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ephemeralRunner.Status.Failures) > maxFailures {
|
||||||
|
log.Info(fmt.Sprintf("EphemeralRunner has failed more than %d times. Deleting ephemeral runner so it can be re-created", maxFailures))
|
||||||
|
if err := r.Delete(ctx, ephemeralRunner); err != nil {
|
||||||
|
log.Error(fmt.Errorf("failed to delete ephemeral runner after %d failures: %w", maxFailures, err), "Failed to delete ephemeral runner")
|
||||||
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctrl.Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
now := metav1.Now()
|
||||||
|
lastFailure := ephemeralRunner.Status.LastFailure()
|
||||||
|
backoffDuration := failedRunnerBackoff[len(ephemeralRunner.Status.Failures)]
|
||||||
|
nextReconciliation := lastFailure.Add(backoffDuration)
|
||||||
|
if !lastFailure.IsZero() && now.Before(&metav1.Time{Time: nextReconciliation}) {
|
||||||
|
log.Info("Backing off the next reconciliation due to failure",
|
||||||
|
"lastFailure", lastFailure,
|
||||||
|
"nextReconciliation", nextReconciliation,
|
||||||
|
"requeueAfter", nextReconciliation.Sub(now.Time),
|
||||||
|
)
|
||||||
|
return ctrl.Result{RequeueAfter: now.Sub(nextReconciliation)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
secret := new(corev1.Secret)
|
secret := new(corev1.Secret)
|
||||||
if err := r.Get(ctx, req.NamespacedName, secret); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, secret); err != nil {
|
||||||
if !kerrors.IsNotFound(err) {
|
if !kerrors.IsNotFound(err) {
|
||||||
|
|
@ -196,39 +233,28 @@ func (r *EphemeralRunnerReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||||
|
|
||||||
pod := new(corev1.Pod)
|
pod := new(corev1.Pod)
|
||||||
if err := r.Get(ctx, req.NamespacedName, pod); err != nil {
|
if err := r.Get(ctx, req.NamespacedName, pod); err != nil {
|
||||||
switch {
|
if !kerrors.IsNotFound(err) {
|
||||||
case !kerrors.IsNotFound(err):
|
|
||||||
log.Error(err, "Failed to fetch the pod")
|
log.Error(err, "Failed to fetch the pod")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
case len(ephemeralRunner.Status.Failures) > 5:
|
// Pod was not found. Create if the pod has never been created
|
||||||
log.Info("EphemeralRunner has failed more than 5 times. Marking it as failed")
|
log.Info("Creating new EphemeralRunner pod.")
|
||||||
errMessage := fmt.Sprintf("Pod has failed to start more than 5 times: %s", pod.Status.Message)
|
result, err := r.createPod(ctx, ephemeralRunner, secret, log)
|
||||||
if err := r.markAsFailed(ctx, ephemeralRunner, errMessage, ReasonTooManyPodFailures, log); err != nil {
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
return result, nil
|
||||||
|
case kerrors.IsInvalid(err) || kerrors.IsForbidden(err):
|
||||||
|
log.Error(err, "Failed to create a pod due to unrecoverable failure")
|
||||||
|
errMessage := fmt.Sprintf("Failed to create the pod: %v", err)
|
||||||
|
if err := r.markAsFailed(ctx, ephemeralRunner, errMessage, ReasonInvalidPodFailure, log); err != nil {
|
||||||
log.Error(err, "Failed to set ephemeral runner to phase Failed")
|
log.Error(err, "Failed to set ephemeral runner to phase Failed")
|
||||||
return ctrl.Result{}, err
|
return ctrl.Result{}, err
|
||||||
}
|
}
|
||||||
return ctrl.Result{}, nil
|
return ctrl.Result{}, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Pod was not found. Create if the pod has never been created
|
log.Error(err, "Failed to create the pod")
|
||||||
log.Info("Creating new EphemeralRunner pod.")
|
return ctrl.Result{}, err
|
||||||
result, err := r.createPod(ctx, ephemeralRunner, secret, log)
|
|
||||||
switch {
|
|
||||||
case err == nil:
|
|
||||||
return result, nil
|
|
||||||
case kerrors.IsInvalid(err) || kerrors.IsForbidden(err):
|
|
||||||
log.Error(err, "Failed to create a pod due to unrecoverable failure")
|
|
||||||
errMessage := fmt.Sprintf("Failed to create the pod: %v", err)
|
|
||||||
if err := r.markAsFailed(ctx, ephemeralRunner, errMessage, ReasonInvalidPodFailure, log); err != nil {
|
|
||||||
log.Error(err, "Failed to set ephemeral runner to phase Failed")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
return ctrl.Result{}, nil
|
|
||||||
default:
|
|
||||||
log.Error(err, "Failed to create the pod")
|
|
||||||
return ctrl.Result{}, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -484,9 +510,9 @@ func (r *EphemeralRunnerReconciler) deletePodAsFailed(ctx context.Context, ephem
|
||||||
log.Info("Updating ephemeral runner status to track the failure count")
|
log.Info("Updating ephemeral runner status to track the failure count")
|
||||||
if err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
if err := patchSubResource(ctx, r.Status(), ephemeralRunner, func(obj *v1alpha1.EphemeralRunner) {
|
||||||
if obj.Status.Failures == nil {
|
if obj.Status.Failures == nil {
|
||||||
obj.Status.Failures = make(map[string]bool)
|
obj.Status.Failures = make(map[string]metav1.Time)
|
||||||
}
|
}
|
||||||
obj.Status.Failures[string(pod.UID)] = true
|
obj.Status.Failures[string(pod.UID)] = metav1.Now()
|
||||||
obj.Status.Ready = false
|
obj.Status.Ready = false
|
||||||
obj.Status.Reason = pod.Status.Reason
|
obj.Status.Reason = pod.Status.Reason
|
||||||
obj.Status.Message = pod.Status.Message
|
obj.Status.Message = pod.Status.Message
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ephemeralRunnerTimeout = time.Second * 20
|
ephemeralRunnerTimeout = time.Second * 20
|
||||||
ephemeralRunnerInterval = time.Millisecond * 250
|
ephemeralRunnerInterval = time.Millisecond * 10
|
||||||
runnerImage = "ghcr.io/actions/actions-runner:latest"
|
runnerImage = "ghcr.io/actions/actions-runner:latest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -528,44 +528,26 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
).Should(BeEquivalentTo(""))
|
).Should(BeEquivalentTo(""))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("It should not re-create pod indefinitely", func() {
|
It("It should eventually delete ephemeral runner after consecutive failures", func() {
|
||||||
updated := new(v1alpha1.EphemeralRunner)
|
updated := new(v1alpha1.EphemeralRunner)
|
||||||
pod := new(corev1.Pod)
|
|
||||||
Eventually(
|
Eventually(
|
||||||
func() (bool, error) {
|
func() error {
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated)
|
return k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod)
|
|
||||||
if err != nil {
|
|
||||||
if kerrors.IsNotFound(err) && len(updated.Status.Failures) > 5 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{
|
|
||||||
Name: v1alpha1.EphemeralRunnerContainerName,
|
|
||||||
State: corev1.ContainerState{
|
|
||||||
Terminated: &corev1.ContainerStateTerminated{
|
|
||||||
ExitCode: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
err = k8sClient.Status().Update(ctx, pod)
|
|
||||||
Expect(err).To(BeNil(), "Failed to update pod status")
|
|
||||||
return false, fmt.Errorf("pod haven't failed for 5 times.")
|
|
||||||
},
|
},
|
||||||
ephemeralRunnerTimeout,
|
ephemeralRunnerTimeout,
|
||||||
ephemeralRunnerInterval,
|
ephemeralRunnerInterval,
|
||||||
).Should(BeEquivalentTo(true), "we should stop creating pod after 5 failures")
|
).Should(Succeed(), "failed to get ephemeral runner")
|
||||||
|
|
||||||
|
failEphemeralRunnerPod := func() *corev1.Pod {
|
||||||
|
pod := new(corev1.Pod)
|
||||||
|
Eventually(
|
||||||
|
func() error {
|
||||||
|
return k8sClient.Get(ctx, client.ObjectKey{Name: updated.Name, Namespace: updated.Namespace}, pod)
|
||||||
|
},
|
||||||
|
ephemeralRunnerTimeout,
|
||||||
|
ephemeralRunnerInterval,
|
||||||
|
).Should(Succeed(), "failed to get ephemeral runner pod")
|
||||||
|
|
||||||
// In case we still have pod created due to controller-runtime cache delay, mark the container as exited
|
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod)
|
|
||||||
if err == nil {
|
|
||||||
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{
|
pod.Status.ContainerStatuses = append(pod.Status.ContainerStatuses, corev1.ContainerStatus{
|
||||||
Name: v1alpha1.EphemeralRunnerContainerName,
|
Name: v1alpha1.EphemeralRunnerContainerName,
|
||||||
State: corev1.ContainerState{
|
State: corev1.ContainerState{
|
||||||
|
|
@ -576,25 +558,70 @@ var _ = Describe("EphemeralRunner", func() {
|
||||||
})
|
})
|
||||||
err := k8sClient.Status().Update(ctx, pod)
|
err := k8sClient.Status().Update(ctx, pod)
|
||||||
Expect(err).To(BeNil(), "Failed to update pod status")
|
Expect(err).To(BeNil(), "Failed to update pod status")
|
||||||
|
|
||||||
|
return pod
|
||||||
}
|
}
|
||||||
|
|
||||||
// EphemeralRunner should failed with reason TooManyPodFailures
|
for i := range 5 {
|
||||||
Eventually(func() (string, error) {
|
pod := failEphemeralRunnerPod()
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return updated.Status.Reason, nil
|
|
||||||
}, ephemeralRunnerTimeout, ephemeralRunnerInterval).Should(BeEquivalentTo("TooManyPodFailures"), "Reason should be TooManyPodFailures")
|
|
||||||
|
|
||||||
// EphemeralRunner should not have any pod
|
Eventually(
|
||||||
Eventually(func() (bool, error) {
|
func() (int, error) {
|
||||||
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod)
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
return false, nil
|
return 0, err
|
||||||
}
|
}
|
||||||
return kerrors.IsNotFound(err), nil
|
return len(updated.Status.Failures), nil
|
||||||
}, ephemeralRunnerTimeout, ephemeralRunnerInterval).Should(BeEquivalentTo(true))
|
},
|
||||||
|
ephemeralRunnerTimeout,
|
||||||
|
ephemeralRunnerInterval,
|
||||||
|
).Should(BeEquivalentTo(i + 1))
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func() error {
|
||||||
|
nextPod := new(corev1.Pod)
|
||||||
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: pod.Name, Namespace: pod.Namespace}, nextPod)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if nextPod.UID != pod.UID {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("pod not recreated")
|
||||||
|
},
|
||||||
|
).WithTimeout(20*time.Second).WithPolling(10*time.Millisecond).Should(Succeed(), "pod should be recreated")
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func() (bool, error) {
|
||||||
|
pod := new(corev1.Pod)
|
||||||
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, pod)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, cs := range pod.Status.ContainerStatuses {
|
||||||
|
if cs.Name == v1alpha1.EphemeralRunnerContainerName {
|
||||||
|
return cs.State.Terminated == nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
).WithTimeout(20*time.Second).WithPolling(10*time.Millisecond).Should(BeEquivalentTo(true), "pod should be terminated")
|
||||||
|
}
|
||||||
|
|
||||||
|
failEphemeralRunnerPod()
|
||||||
|
|
||||||
|
Eventually(
|
||||||
|
func() (bool, error) {
|
||||||
|
err := k8sClient.Get(ctx, client.ObjectKey{Name: ephemeralRunner.Name, Namespace: ephemeralRunner.Namespace}, updated)
|
||||||
|
if kerrors.IsNotFound(err) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, err
|
||||||
|
},
|
||||||
|
ephemeralRunnerTimeout,
|
||||||
|
ephemeralRunnerInterval,
|
||||||
|
).Should(BeTrue(), "Ephemeral runner should eventually be deleted")
|
||||||
})
|
})
|
||||||
|
|
||||||
It("It should re-create pod on eviction", func() {
|
It("It should re-create pod on eviction", func() {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
|
@ -21,6 +22,7 @@ import (
|
||||||
"github.com/go-logr/logr"
|
"github.com/go-logr/logr"
|
||||||
. "github.com/onsi/ginkgo/v2"
|
. "github.com/onsi/ginkgo/v2"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
||||||
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
"github.com/actions/actions-runner-controller/apis/actions.github.com/v1alpha1"
|
||||||
|
|
@ -35,6 +37,10 @@ const (
|
||||||
ephemeralRunnerSetTestGitHubToken = "gh_token"
|
ephemeralRunnerSetTestGitHubToken = "gh_token"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestPrecomputedConstants(t *testing.T) {
|
||||||
|
require.Equal(t, len(failedRunnerBackoff), maxFailures+1)
|
||||||
|
}
|
||||||
|
|
||||||
var _ = Describe("Test EphemeralRunnerSet controller", func() {
|
var _ = Describe("Test EphemeralRunnerSet controller", func() {
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
var mgr ctrl.Manager
|
var mgr ctrl.Manager
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo/config"
|
"github.com/onsi/ginkgo/config"
|
||||||
|
|
||||||
|
|
@ -79,6 +80,15 @@ var _ = BeforeSuite(func() {
|
||||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(k8sClient).ToNot(BeNil())
|
Expect(k8sClient).ToNot(BeNil())
|
||||||
|
|
||||||
|
failedRunnerBackoff = []time.Duration{
|
||||||
|
20 * time.Millisecond,
|
||||||
|
20 * time.Millisecond,
|
||||||
|
20 * time.Millisecond,
|
||||||
|
20 * time.Millisecond,
|
||||||
|
20 * time.Millisecond,
|
||||||
|
20 * time.Millisecond,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
var _ = AfterSuite(func() {
|
var _ = AfterSuite(func() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue