package oapi import ( "context" "errors" "fmt" "net/http" "time" v2 "github.com/exoscale/egoscale/v2/api" ) const ( // DefaultPollingInterval represents the default polling interval used in Poller. DefaultPollingInterval = 3 * time.Second operationStatePending = "pending" operationStateSuccess = "success" operationStateFailure = "failure" operationStateTimeout = "timeout" ) // PollFunc represents a function invoked periodically in a polling loop. It returns a boolean flag // true if the job is completed or false if polling must continue, and any error that occurred // during the polling (which interrupts the polling regardless of the boolean flag value). // Upon successful completion, an interface descring an opaque operation can be returned to the // caller, which will have to perform type assertion depending on the PollFunc implementation. type PollFunc func(ctx context.Context) (bool, interface{}, error) // Poller represents a poller instance. type Poller struct { interval time.Duration timeout time.Duration } // NewPoller returns a Poller instance. func NewPoller() *Poller { return &Poller{ interval: DefaultPollingInterval, } } // WithInterval sets the interval at which the polling function will be executed (default: 3s). func (p *Poller) WithInterval(interval time.Duration) *Poller { if interval > 0 { p.interval = interval } return p } // WithTimeout sets the time out value after which the polling routine will be cancelled // (default: no time out). func (p *Poller) WithTimeout(timeout time.Duration) *Poller { if timeout > 0 { p.timeout = timeout } return p } // Poll starts the polling routine, executing the provided polling function at the configured // polling interval. Upon successful polling, an opaque operation is returned to the caller, which // actual type has to asserted depending on the PollFunc executed. func (p *Poller) Poll(ctx context.Context, pf PollFunc) (interface{}, error) { if p.timeout > 0 { ctxWithTimeout, cancel := context.WithTimeout(ctx, p.timeout) defer cancel() ctx = ctxWithTimeout } ticker := time.NewTicker(p.interval) defer ticker.Stop() for { select { case <-ticker.C: done, res, err := pf(ctx) if err != nil { return nil, err } if !done { continue } return res, nil case <-ctx.Done(): return nil, ctx.Err() } } } // OperationPoller returns a PollFunc function which queries the state of the specified job. // Upon successful job completion, the type of the interface{} returned by the PollFunc is a // pointer to a Resource object (*Resource). func OperationPoller(client ClientWithResponsesInterface, zone string, id string) PollFunc { return func(ctx context.Context) (bool, interface{}, error) { resp, err := client.GetOperationWithResponse(v2.WithZone(ctx, zone), id) if err != nil { return true, nil, err } if resp.StatusCode() != http.StatusOK { return true, nil, fmt.Errorf("unexpected response from API: %s", resp.Status()) } switch *resp.JSON200.State { case operationStatePending: return false, nil, nil case operationStateSuccess: return true, resp.JSON200.Reference, nil case operationStateFailure: return true, nil, errors.New("job failed") case operationStateTimeout: return true, nil, errors.New("job timed out") default: return true, nil, fmt.Errorf("unknown job state: %s", *resp.JSON200.State) } } }