~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/meterstatus/isolated.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2015 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package meterstatus
 
5
 
 
6
import (
 
7
        "time"
 
8
 
 
9
        "github.com/juju/errors"
 
10
        "github.com/juju/utils/clock"
 
11
        "gopkg.in/juju/charm.v6-unstable/hooks"
 
12
        "launchpad.net/tomb"
 
13
 
 
14
        "github.com/juju/juju/worker"
 
15
        "github.com/juju/juju/worker/uniter/runner/context"
 
16
)
 
17
 
 
18
const (
 
19
        // defaultAmberGracePeriod is the time that the unit is allowed to
 
20
        // function without a working API connection before its meter
 
21
        // status is switched to AMBER.
 
22
        defaultAmberGracePeriod = time.Minute * 5
 
23
 
 
24
        // defaultRedGracePeriod is the time that a unit is allowed to function
 
25
        // without a working API connection before its meter status is
 
26
        // switched to RED.
 
27
        defaultRedGracePeriod = time.Minute * 15
 
28
)
 
29
 
 
30
// workerState defines all the possible states the isolatedStatusWorker can be in.
 
31
type WorkerState int
 
32
 
 
33
const (
 
34
        Uninitialized WorkerState = iota
 
35
        WaitingAmber              // Waiting for a signal to switch to AMBER status.
 
36
        WaitingRed                // Waiting for a signal to switch to RED status.
 
37
        Done                      // No more transitions to perform.
 
38
)
 
39
 
 
40
// IsolatedConfig stores all the dependencies required to create an isolated meter status worker.
 
41
type IsolatedConfig struct {
 
42
        Runner           HookRunner
 
43
        StateFile        *StateFile
 
44
        Clock            clock.Clock
 
45
        AmberGracePeriod time.Duration
 
46
        RedGracePeriod   time.Duration
 
47
        TriggerFactory   TriggerCreator
 
48
}
 
49
 
 
50
// Validate validates the config structure and returns an error on failure.
 
51
func (c IsolatedConfig) Validate() error {
 
52
        if c.Runner == nil {
 
53
                return errors.New("hook runner not provided")
 
54
        }
 
55
        if c.StateFile == nil {
 
56
                return errors.New("state file not provided")
 
57
        }
 
58
        if c.Clock == nil {
 
59
                return errors.New("clock not provided")
 
60
        }
 
61
        if c.AmberGracePeriod <= 0 {
 
62
                return errors.New("invalid amber grace period")
 
63
        }
 
64
        if c.RedGracePeriod <= 0 {
 
65
                return errors.New("invalid red grace period")
 
66
        }
 
67
        if c.AmberGracePeriod >= c.RedGracePeriod {
 
68
                return errors.New("amber grace period must be shorter than the red grace period")
 
69
        }
 
70
        return nil
 
71
}
 
72
 
 
73
// isolatedStatusWorker is a worker that is instantiated by the
 
74
// meter status manifold when the API connection is unavailable.
 
75
// Its main function is to escalate the meter status of the unit
 
76
// to amber and later to red.
 
77
type isolatedStatusWorker struct {
 
78
        config IsolatedConfig
 
79
 
 
80
        tomb tomb.Tomb
 
81
}
 
82
 
 
83
// NewIsolatedStatusWorker creates a new status worker that runs without an API connection.
 
84
func NewIsolatedStatusWorker(cfg IsolatedConfig) (worker.Worker, error) {
 
85
        if err := cfg.Validate(); err != nil {
 
86
                return nil, errors.Trace(err)
 
87
        }
 
88
        w := &isolatedStatusWorker{
 
89
                config: cfg,
 
90
        }
 
91
        go func() {
 
92
                defer w.tomb.Done()
 
93
                w.tomb.Kill(w.loop())
 
94
        }()
 
95
        return w, nil
 
96
}
 
97
 
 
98
func (w *isolatedStatusWorker) loop() error {
 
99
        code, info, disconnected, err := w.config.StateFile.Read()
 
100
        if err != nil {
 
101
                return errors.Trace(err)
 
102
        }
 
103
 
 
104
        // Disconnected time has not been recorded yet.
 
105
        if disconnected == nil {
 
106
                disconnected = &Disconnected{w.config.Clock.Now().Unix(), WaitingAmber}
 
107
        }
 
108
 
 
109
        amberSignal, redSignal := w.config.TriggerFactory(disconnected.State, code, disconnected.When(), w.config.Clock, w.config.AmberGracePeriod, w.config.RedGracePeriod)
 
110
        for {
 
111
                select {
 
112
                case <-w.tomb.Dying():
 
113
                        return tomb.ErrDying
 
114
                case <-redSignal:
 
115
                        logger.Debugf("triggering meter status transition to RED due to loss of connection")
 
116
                        currentCode := "RED"
 
117
                        currentInfo := "unit agent has been disconnected"
 
118
 
 
119
                        w.applyStatus(currentCode, currentInfo)
 
120
                        code, info = currentCode, currentInfo
 
121
                        disconnected.State = Done
 
122
                case <-amberSignal:
 
123
                        logger.Debugf("triggering meter status transition to AMBER due to loss of connection")
 
124
                        currentCode := "AMBER"
 
125
                        currentInfo := "unit agent has been disconnected"
 
126
 
 
127
                        w.applyStatus(currentCode, currentInfo)
 
128
                        code, info = currentCode, currentInfo
 
129
                        disconnected.State = WaitingRed
 
130
                }
 
131
                err := w.config.StateFile.Write(code, info, disconnected)
 
132
                if err != nil {
 
133
                        return errors.Annotate(err, "failed to record meter status worker state")
 
134
                }
 
135
        }
 
136
}
 
137
 
 
138
func (w *isolatedStatusWorker) applyStatus(code, info string) {
 
139
        logger.Tracef("applying meter status change: %q (%q)", code, info)
 
140
        err := w.config.Runner.RunHook(code, info, w.tomb.Dying())
 
141
        cause := errors.Cause(err)
 
142
        switch {
 
143
        case context.IsMissingHookError(cause):
 
144
                logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged))
 
145
        case err != nil:
 
146
                logger.Errorf("meter status worker encountered hook error: %v", err)
 
147
        }
 
148
}
 
149
 
 
150
// Kill is part of the worker.Worker interface.
 
151
func (w *isolatedStatusWorker) Kill() {
 
152
        w.tomb.Kill(nil)
 
153
}
 
154
 
 
155
// Wait is part of the worker.Worker interface.
 
156
func (w *isolatedStatusWorker) Wait() error {
 
157
        return w.tomb.Wait()
 
158
}