Skip to content

Commit 358ae8d

Browse files
committed
Fix doNotGrantBefore grant timing
1 parent 5cd846e commit 358ae8d

2 files changed

Lines changed: 93 additions & 9 deletions

File tree

internal/controller/nodedisruption_controller.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -280,14 +280,6 @@ func (ndr *SingleNodeDisruptionReconciler) getDoNotGrantBeforeDate() metav1.Time
280280
func (ndr *SingleNodeDisruptionReconciler) tryTransitionToGranted(ctx context.Context) (err error) {
281281
logger := log.FromContext(ctx)
282282
doNotGrantBefore := ndr.getDoNotGrantBeforeDate()
283-
284-
if !doNotGrantBefore.IsZero() && time.Now().Before(doNotGrantBefore.Time) {
285-
logger.Info("Grant deferred until doNotGrantBefore", "doNotGrantBefore", doNotGrantBefore.Time)
286-
ndr.NodeDisruption.Status.State = nodedisruptionv1alpha1.Pending
287-
ndr.NodeDisruption.Status.NextRetryDate = doNotGrantBefore
288-
return nil
289-
}
290-
291283
nextRetryDate := ndr.getRetryDate()
292284

293285
var state nodedisruptionv1alpha1.NodeDisruptionState
@@ -307,7 +299,13 @@ func (ndr *SingleNodeDisruptionReconciler) tryTransitionToGranted(ctx context.Co
307299
}
308300

309301
if !anyFailed {
310-
state = nodedisruptionv1alpha1.Granted
302+
if !doNotGrantBefore.IsZero() && time.Now().Before(doNotGrantBefore.Time) {
303+
logger.Info("Grant deferred until doNotGrantBefore", "doNotGrantBefore", doNotGrantBefore.Time)
304+
state = nodedisruptionv1alpha1.Pending
305+
nextRetryDate = doNotGrantBefore
306+
} else {
307+
state = nodedisruptionv1alpha1.Granted
308+
}
311309
} else {
312310
if !nextRetryDate.IsZero() {
313311
state = nodedisruptionv1alpha1.Pending

internal/controller/nodedisruption_controller_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,92 @@ var _ = Describe("NodeDisruption controller", func() {
360360
return errorsk8s.IsNotFound(err)
361361
}, timeout, interval).Should(BeTrue())
362362
})
363+
364+
It("runs prepare and ready hooks before doNotGrantBefore while keeping the disruption pending", func() {
365+
mockBasePath := "/api/v2"
366+
367+
By("Starting an http server to receive the hook")
368+
prepareCalledCnt := 0
369+
readyCalledCnt := 0
370+
371+
checkHookFn := func(w http.ResponseWriter, req *http.Request) {
372+
switch req.URL.Path {
373+
case mockBasePath + "/prepare":
374+
prepareCalledCnt++
375+
case mockBasePath + "/ready":
376+
readyCalledCnt++
377+
}
378+
Expect(req.Header.Get("Content-Type")).Should(Equal("application/json"))
379+
w.WriteHeader(http.StatusOK)
380+
}
381+
382+
mockServer := startDummyHTTPServer(checkHookFn)
383+
defer mockServer.Close()
384+
385+
By("creating a budget that accepts one disruption")
386+
adb := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{
387+
TypeMeta: metav1.TypeMeta{
388+
APIVersion: "nodedisruption.criteo.com/v1alpha1",
389+
Kind: "ApplicationDisruptionBudget",
390+
},
391+
ObjectMeta: metav1.ObjectMeta{
392+
Name: "test-do-not-grant-before",
393+
Namespace: "default",
394+
},
395+
Spec: nodedisruptionv1alpha1.ApplicationDisruptionBudgetSpec{
396+
PodSelector: metav1.LabelSelector{MatchLabels: podLabels},
397+
MaxDisruptions: 1,
398+
HookV2BasePath: nodedisruptionv1alpha1.HookSpec{
399+
URL: mockServer.URL + mockBasePath,
400+
},
401+
},
402+
}
403+
Expect(k8sClient.Create(ctx, adb)).Should(Succeed())
404+
405+
By("checking the ApplicationDisruptionBudget is synchronized")
406+
ADBLookupKey := types.NamespacedName{Name: "test-do-not-grant-before", Namespace: "default"}
407+
createdADB := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{}
408+
Eventually(func() []string {
409+
err := k8sClient.Get(ctx, ADBLookupKey, createdADB)
410+
Expect(err).Should(Succeed())
411+
return createdADB.Status.WatchedNodes
412+
}, timeout, interval).Should(Equal([]string{"node1"}))
413+
414+
By("creating a new NodeDisruption with doNotGrantBefore in the future")
415+
doNotGrantBefore := metav1.NewTime(time.Now().Add(time.Hour).Truncate(time.Second))
416+
disruption := &nodedisruptionv1alpha1.NodeDisruption{
417+
TypeMeta: metav1.TypeMeta{
418+
APIVersion: "nodedisruption.criteo.com/v1alpha1",
419+
Kind: "NodeDisruption",
420+
},
421+
ObjectMeta: metav1.ObjectMeta{
422+
Name: NDName,
423+
Namespace: NDNamespace,
424+
},
425+
Spec: nodedisruptionv1alpha1.NodeDisruptionSpec{
426+
NodeSelector: metav1.LabelSelector{MatchLabels: nodeLabels1},
427+
Type: "maintenance",
428+
DoNotGrantBefore: doNotGrantBefore,
429+
},
430+
}
431+
Expect(k8sClient.Create(ctx, disruption.DeepCopy())).Should(Succeed())
432+
433+
NDLookupKey := types.NamespacedName{Name: NDName, Namespace: NDNamespace}
434+
createdDisruption := &nodedisruptionv1alpha1.NodeDisruption{}
435+
436+
By("checking the NodeDisruption stays pending after the hooks have completed")
437+
Eventually(func(g Gomega) {
438+
err := k8sClient.Get(ctx, NDLookupKey, createdDisruption)
439+
g.Expect(err).ToNot(HaveOccurred())
440+
g.Expect(createdDisruption.Status.State).To(Equal(nodedisruptionv1alpha1.Pending))
441+
g.Expect(createdDisruption.Status.NextRetryDate.Time).To(Equal(doNotGrantBefore.Time))
442+
g.Expect(createdDisruption.Status.DisruptedDisruptionBudgets).To(HaveLen(1))
443+
g.Expect(createdDisruption.Status.DisruptedDisruptionBudgets[0].Preparing).To(BeTrue())
444+
g.Expect(createdDisruption.Status.DisruptedDisruptionBudgets[0].Ok).To(BeTrue())
445+
g.Expect(prepareCalledCnt).To(Equal(1))
446+
g.Expect(readyCalledCnt).To(Equal(1))
447+
}, timeout, interval).Should(Succeed())
448+
})
363449
})
364450

365451
When("there are no budgets in the cluster", func() {

0 commit comments

Comments
 (0)