Skip to content

Commit e681ef3

Browse files
authored
Adds forbidParallelRuns configuration option (#31)
1 parent 59487e2 commit e681ef3

File tree

4 files changed

+93
-0
lines changed

4 files changed

+93
-0
lines changed

api/v1beta1/schedulercanary_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ type SchedulerCanarySpec struct {
4242
// The default is 1 minute.
4343
Interval metav1.Duration `json:"interval,omitempty"`
4444

45+
// ForbidParallelRuns will prevent the creation of a new canary pod if there is already a canary pod running.
46+
// The default is false.
47+
ForbidParallelRuns bool `json:"forbidParallelRuns,omitempty"`
48+
4549
// PodTemplate is the pod template to use for the canary pods.
4650
PodTemplate corev1.PodTemplateSpec `json:"podTemplate,omitempty"`
4751
}

config/crd/bases/monitoring.appuio.io_schedulercanaries.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ spec:
4343
spec:
4444
description: SchedulerCanarySpec defines the desired state of SchedulerCanary
4545
properties:
46+
forbidParallelRuns:
47+
description: |-
48+
ForbidParallelRuns will prevent the creation of a new canary pod if there is already a canary pod running.
49+
The default is false.
50+
type: boolean
4651
interval:
4752
description: |-
4853
Interval is the interval at which a canary pod will be created.

controllers/schedulercanary_controller.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ package controllers
1818

1919
import (
2020
"context"
21+
"fmt"
2122
"time"
2223

24+
corev1 "k8s.io/api/core/v1"
2325
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2426
"k8s.io/apimachinery/pkg/runtime"
2527
ctrl "sigs.k8s.io/controller-runtime"
@@ -68,6 +70,22 @@ func (r *SchedulerCanaryReconciler) Reconcile(ctx context.Context, req ctrl.Requ
6870
return ctrl.Result{Requeue: true, RequeueAfter: rqi}, nil
6971
}
7072

73+
if instance.Spec.ForbidParallelRuns {
74+
// Check if there is already a canary pod running.
75+
pods := &corev1.PodList{}
76+
if err := r.Client.List(ctx, pods, client.InNamespace(instance.Namespace), client.MatchingLabels{instanceLabel: instance.Name}); err != nil {
77+
return ctrl.Result{}, fmt.Errorf("forbidParallelRuns: error checking for already running pods, failed to list pods: %w", err)
78+
}
79+
if len(pods.Items) > 0 {
80+
podNames := make([]string, len(pods.Items))
81+
for i, pod := range pods.Items {
82+
podNames[i] = pod.Name
83+
}
84+
l.Info("ForbidParallelRuns: already running pods found, skipping pod creation", "pods", podNames)
85+
return ctrl.Result{}, nil
86+
}
87+
}
88+
7189
if err := r.createCanaryPod(ctx, instance); err != nil {
7290
return ctrl.Result{}, err
7391
}
@@ -85,6 +103,7 @@ func (r *SchedulerCanaryReconciler) Reconcile(ctx context.Context, req ctrl.Requ
85103
func (r *SchedulerCanaryReconciler) SetupWithManager(mgr ctrl.Manager) error {
86104
return ctrl.NewControllerManagedBy(mgr).
87105
For(&monitoringv1beta1.SchedulerCanary{}).
106+
Owns(&corev1.Pod{}).
88107
Complete(r)
89108
}
90109

controllers/schedulercanary_controller_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,71 @@ var _ = Describe("SchedulerCanary controller", func() {
7070

7171
})
7272

73+
Context("ForbidParallelRuns", func() {
74+
BeforeEach(func() {
75+
ctx := context.Background()
76+
77+
schedulerCanary := &monitoringv1beta1.SchedulerCanary{
78+
ObjectMeta: metav1.ObjectMeta{
79+
Name: "my-canary",
80+
Namespace: "default",
81+
},
82+
Spec: monitoringv1beta1.SchedulerCanarySpec{
83+
Interval: metav1.Duration{Duration: time.Millisecond},
84+
MaxPodCompletionTimeout: metav1.Duration{Duration: time.Minute},
85+
ForbidParallelRuns: true,
86+
PodTemplate: corev1.PodTemplateSpec{
87+
Spec: corev1.PodSpec{
88+
Containers: []corev1.Container{
89+
{
90+
Name: "scheduler-canary",
91+
Image: "busybox",
92+
},
93+
},
94+
},
95+
},
96+
},
97+
}
98+
Expect(k8sClient.Create(ctx, schedulerCanary)).Should(Succeed())
99+
})
100+
101+
It("ForbidParallelRuns: should not create more than one pod", func() {
102+
ctx := context.Background()
103+
104+
Eventually(func() (int, error) {
105+
pods := &corev1.PodList{}
106+
107+
err := k8sClient.List(ctx, pods, client.InNamespace("default"), client.MatchingLabels(map[string]string{
108+
instanceLabel: "my-canary",
109+
}))
110+
111+
return len(pods.Items), err
112+
}, "10s", "250ms").Should(BeNumerically(">=", 1))
113+
114+
Consistently(func() (int, error) {
115+
pods := &corev1.PodList{}
116+
117+
err := k8sClient.List(ctx, pods, client.InNamespace("default"), client.MatchingLabels(map[string]string{
118+
instanceLabel: "my-canary",
119+
}))
120+
121+
return len(pods.Items), err
122+
}, "5s", "250ms").Should(Equal(1))
123+
124+
Expect(k8sClient.DeleteAllOf(ctx, &corev1.Pod{}, client.InNamespace("default"))).Should(Succeed())
125+
126+
Eventually(func() (int, error) {
127+
pods := &corev1.PodList{}
128+
129+
err := k8sClient.List(ctx, pods, client.InNamespace("default"), client.MatchingLabels(map[string]string{
130+
instanceLabel: "my-canary",
131+
}))
132+
133+
return len(pods.Items), err
134+
}, "10s", "250ms").Should(BeNumerically(">=", 1))
135+
})
136+
})
137+
73138
AfterEach(func() {
74139
ctx := context.Background()
75140

0 commit comments

Comments
 (0)