func processDeadEnv(client apiundertaker.UndertakerClient, clock uc.Clock, tod time.Time, stopCh <-chan struct{}) error { timeDead := clock.Now().Sub(tod) wait := ripTime - timeDead if wait < 0 { wait = 0 } select { case <-clock.After(wait): err := client.RemoveEnviron() return errors.Annotate(err, "could not remove all docs for dead environment") case <-stopCh: return tomb.ErrDying } }
// New returns a worker which periodically prunes the data for // completed transactions. func New(tp TransactionPruner, interval time.Duration, clock clock.Clock) worker.Worker { return worker.NewSimpleWorker(func(stopCh <-chan struct{}) error { for { select { case <-clock.After(interval): err := tp.MaybePruneTransactions() if err != nil { return errors.Annotate(err, "pruning failed, txnpruner stopping") } case <-stopCh: return nil } } }) }
// newTimedStatusUpdater returns a function which waits a given period of time // before querying the apiserver for updated data. func newTimedStatusUpdater(ctx *cmd.Context, api destroyControllerAPI, controllerModelUUID string, clock clock.Clock) func(time.Duration) (ctrData, []modelData) { return func(wait time.Duration) (ctrData, []modelData) { if wait > 0 { <-clock.After(wait) } // If we hit an error, status.HostedModelCount will be 0, the polling // loop will stop and we'll go directly to destroying the model. ctrStatus, modelsStatus, err := newData(api, controllerModelUUID) if err != nil { ctx.Infof("Unable to get the controller summary from the API: %s.", err) } return ctrStatus, modelsStatus } }
// runCommandsWithTimeout is a helper to abstract common code between run commands and // juju-run as an action func (runner *runner) runCommandsWithTimeout(commands string, timeout time.Duration, clock clock.Clock) (*utilexec.ExecResponse, error) { srv, err := runner.startJujucServer() if err != nil { return nil, err } defer srv.Close() env, err := runner.context.HookVars(runner.paths) if err != nil { return nil, errors.Trace(err) } command := utilexec.RunParams{ Commands: commands, WorkingDir: runner.paths.GetCharmDir(), Environment: env, Clock: clock, } err = command.Run() if err != nil { return nil, err } runner.context.SetProcess(hookProcess{command.Process()}) var cancel chan struct{} if timeout != 0 { cancel = make(chan struct{}) go func() { <-clock.After(timeout) close(cancel) }() } // Block and wait for process to finish return command.WaitWithCancel(cancel) }
// GetTriggers returns the signal channels for state transitions based on the current state. // It controls the transitions of the inactive meter status worker. // // In a simple case, the transitions are trivial: // // D------------------A----------------------R---------------------> // // D - disconnect time // A - amber status triggered // R - red status triggered // // The problem arises from the fact that the lifetime of the worker can // be interrupted, possibly with significant portions of the duration missing. func GetTriggers( wst WorkerState, status string, disconnectedAt time.Time, clk clock.Clock, amberGracePeriod time.Duration, redGracePeriod time.Duration) (<-chan time.Time, <-chan time.Time) { now := clk.Now() if wst == Done { return nil, nil } if wst <= WaitingAmber && status == "RED" { // If the current status is already RED, we don't want to deescalate. wst = WaitingRed // } else if wst <= WaitingAmber && now.Sub(disconnectedAt) >= amberGracePeriod { // If we missed the transition to amber, activate it. // wst = WaitingRed } else if wst < Done && now.Sub(disconnectedAt) >= redGracePeriod { // If we missed the transition to amber and it's time to transition to RED, go straight to RED. wst = WaitingRed } if wst == WaitingRed { redSignal := clk.After(redGracePeriod - now.Sub(disconnectedAt)) return nil, redSignal } if wst == WaitingAmber || wst == Uninitialized { amberSignal := clk.After(amberGracePeriod - now.Sub(disconnectedAt)) redSignal := clk.After(redGracePeriod - now.Sub(disconnectedAt)) return amberSignal, redSignal } return nil, nil }
func machineLoop(context machineContext, m machine, lifeChanged <-chan struct{}, clock clock.Clock) error { // Use a short poll interval when initially waiting for // a machine's address and machine agent to start, and a long one when it already // has an address and the machine agent is started. pollInterval := ShortPoll pollInstance := func() error { instInfo, err := pollInstanceInfo(context, m) if err != nil { return err } machineStatus := status.Pending if err == nil { if statusInfo, err := m.Status(); err != nil { logger.Warningf("cannot get current machine status for machine %v: %v", m.Id(), err) } else { // TODO(perrito666) add status validation. machineStatus = status.Status(statusInfo.Status) } } // the extra condition below (checking allocating/pending) is here to improve user experience // without it the instance status will say "pending" for +10 minutes after the agent comes up to "started" if instInfo.status.Status != status.Allocating && instInfo.status.Status != status.Pending { if len(instInfo.addresses) > 0 && machineStatus == status.Started { // We've got at least one address and a status and instance is started, so poll infrequently. pollInterval = LongPoll } else if pollInterval < LongPoll { // We have no addresses or not started - poll increasingly rarely // until we do. pollInterval = time.Duration(float64(pollInterval) * ShortPollBackoff) } } return nil } shouldPollInstance := true for { if shouldPollInstance { if err := pollInstance(); err != nil { if !params.IsCodeNotProvisioned(err) { return errors.Trace(err) } } shouldPollInstance = false } select { case <-context.dying(): return context.errDying() case <-clock.After(pollInterval): shouldPollInstance = true case <-lifeChanged: if err := m.Refresh(); err != nil { return err } if m.Life() == params.Dead { return nil } } } }