constellation/operators/constellation-node-operator/internal/poller/poller.go

128 lines
3.2 KiB
Go
Raw Normal View History

/*
Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package poller implements a poller that can be used to wait for a condition to be met.
// The poller is designed to be a replacement for the azure-sdk-for-go poller
// with exponential backoff and an injectable clock.
// reference: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore@v1.1.1/runtime#Poller .
package poller
import (
"context"
"errors"
"time"
"k8s.io/utils/clock"
)
// PollUntilDoneOptions provides options for the Poller.
// Used to specify backoff and clock options.
type PollUntilDoneOptions struct {
StartingBackoff time.Duration
MaxBackoff time.Duration
clock.Clock
}
// NewPollUntilDoneOptions creates a new PollUntilDoneOptions with the default values and a real clock.
func NewPollUntilDoneOptions() *PollUntilDoneOptions {
return &PollUntilDoneOptions{
Clock: clock.RealClock{},
}
}
// Poller is a poller that can be used to wait for a condition to be met.
// The poller is designed to be a replacement for the azure-sdk-for-go poller
// with exponential backoff and an injectable clock.
// reference: https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/azcore@v1.1.1/runtime#Poller .
type Poller[T any] struct {
handler PollingHandler[T]
err error
result *T
done bool
}
// New creates a new Poller.
func New[T any](handler PollingHandler[T]) *Poller[T] {
return &Poller[T]{
handler: handler,
result: new(T),
}
}
// PollUntilDone polls the handler until the condition is met or the context is cancelled.
func (p *Poller[T]) PollUntilDone(ctx context.Context, options *PollUntilDoneOptions) (T, error) {
if options == nil {
options = NewPollUntilDoneOptions()
}
if options.MaxBackoff == 0 {
options.MaxBackoff = time.Minute
}
if options.StartingBackoff < time.Second {
options.StartingBackoff = time.Second
}
backoff := options.StartingBackoff
for {
timer := options.Clock.NewTimer(backoff)
err := p.Poll(ctx)
if err != nil {
return *new(T), err
}
if p.Done() {
return p.Result(ctx)
}
select {
case <-ctx.Done():
return *new(T), ctx.Err()
case <-timer.C():
}
if backoff >= options.MaxBackoff/2 {
backoff = options.MaxBackoff
} else {
backoff *= 2
}
}
}
// Poll polls the handler.
func (p *Poller[T]) Poll(ctx context.Context) error {
return p.handler.Poll(ctx)
}
// Done returns true if the condition is met.
func (p *Poller[T]) Done() bool {
return p.handler.Done()
}
// Result returns the result of the poller if the condition is met.
// If the condition is not met, an error is returned.
func (p *Poller[T]) Result(ctx context.Context) (T, error) {
if !p.Done() {
return *new(T), errors.New("poller is in a non-terminal state")
}
if p.done {
// the result has already been retrieved, return the cached value
if p.err != nil {
return *new(T), p.err
}
return *p.result, nil
}
err := p.handler.Result(ctx, p.result)
p.done = true
if err != nil {
p.err = err
return *new(T), p.err
}
return *p.result, nil
}
// PollingHandler is a handler that can be used to poll for a condition to be met.
type PollingHandler[T any] interface {
Done() bool
Poll(ctx context.Context) error
Result(ctx context.Context, out *T) error
}