~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/worker/singular/flag.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-2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package singular
 
5
 
 
6
import (
 
7
        "time"
 
8
 
 
9
        "github.com/juju/errors"
 
10
        "github.com/juju/juju/core/lease"
 
11
        "github.com/juju/juju/worker/catacomb"
 
12
        "github.com/juju/utils/clock"
 
13
)
 
14
 
 
15
// Facade exposes the capabilities required by a FlagWorker.
 
16
type Facade interface {
 
17
        Claim(duration time.Duration) error
 
18
        Wait() error
 
19
}
 
20
 
 
21
// FlagConfig holds a FlagWorker's dependencies and resources.
 
22
type FlagConfig struct {
 
23
        Clock    clock.Clock
 
24
        Facade   Facade
 
25
        Duration time.Duration
 
26
}
 
27
 
 
28
// Validate returns an error if the config cannot be expected to run a
 
29
// FlagWorker.
 
30
func (config FlagConfig) Validate() error {
 
31
        if config.Clock == nil {
 
32
                return errors.NotValidf("nil Clock")
 
33
        }
 
34
        if config.Facade == nil {
 
35
                return errors.NotValidf("nil Facade")
 
36
        }
 
37
        if config.Duration <= 0 {
 
38
                return errors.NotValidf("non-positive Duration")
 
39
        }
 
40
        return nil
 
41
}
 
42
 
 
43
// ErrRefresh indicates that the flag's Check result is no longer valid,
 
44
// and a new FlagWorker must be started to get a valid result.
 
45
var ErrRefresh = errors.New("model responsibility unclear, please retry")
 
46
 
 
47
// FlagWorker implements worker.Worker and util.Flag, representing
 
48
// controller ownership of a model, such that the Flag's validity is tied
 
49
// to the Worker's lifetime.
 
50
type FlagWorker struct {
 
51
        catacomb catacomb.Catacomb
 
52
        config   FlagConfig
 
53
        valid    bool
 
54
}
 
55
 
 
56
func NewFlagWorker(config FlagConfig) (*FlagWorker, error) {
 
57
        if err := config.Validate(); err != nil {
 
58
                return nil, errors.Trace(err)
 
59
        }
 
60
        valid, err := claim(config)
 
61
        if err != nil {
 
62
                return nil, errors.Trace(err)
 
63
        }
 
64
        flag := &FlagWorker{
 
65
                config: config,
 
66
                valid:  valid,
 
67
        }
 
68
        err = catacomb.Invoke(catacomb.Plan{
 
69
                Site: &flag.catacomb,
 
70
                Work: flag.run,
 
71
        })
 
72
        if err != nil {
 
73
                return nil, errors.Trace(err)
 
74
        }
 
75
        return flag, nil
 
76
}
 
77
 
 
78
// Kill is part of the worker.Worker interface.
 
79
func (flag *FlagWorker) Kill() {
 
80
        flag.catacomb.Kill(nil)
 
81
}
 
82
 
 
83
// Wait is part of the worker.Worker interface.
 
84
func (flag *FlagWorker) Wait() error {
 
85
        return flag.catacomb.Wait()
 
86
}
 
87
 
 
88
// Check is part of the util.Flag interface.
 
89
//
 
90
// Check returns true if the flag indicates that the configured Identity
 
91
// (i.e. this controller) has taken control of the configured Scope (i.e.
 
92
// the model we want to manage exclusively).
 
93
//
 
94
// The validity of this result is tied to the lifetime of the FlagWorker;
 
95
// once the worker has stopped, no inferences may be drawn from any Check
 
96
// result.
 
97
func (flag *FlagWorker) Check() bool {
 
98
        return flag.valid
 
99
}
 
100
 
 
101
// run invokes a suitable runFunc, depending on the value of .valid.
 
102
func (flag *FlagWorker) run() error {
 
103
        runFunc := waitVacant
 
104
        if flag.valid {
 
105
                runFunc = keepOccupied
 
106
        }
 
107
        err := runFunc(flag.config, flag.catacomb.Dying())
 
108
        return errors.Trace(err)
 
109
}
 
110
 
 
111
// keepOccupied is a runFunc that tries to keep a flag valid.
 
112
func keepOccupied(config FlagConfig, abort <-chan struct{}) error {
 
113
        for {
 
114
                select {
 
115
                case <-abort:
 
116
                        return nil
 
117
                case <-sleep(config):
 
118
                        success, err := claim(config)
 
119
                        if err != nil {
 
120
                                return errors.Trace(err)
 
121
                        }
 
122
                        if !success {
 
123
                                return ErrRefresh
 
124
                        }
 
125
                }
 
126
        }
 
127
}
 
128
 
 
129
// claim claims model ownership on behalf of a controller, and returns
 
130
// true if the attempt succeeded.
 
131
func claim(config FlagConfig) (bool, error) {
 
132
        err := config.Facade.Claim(config.Duration)
 
133
        cause := errors.Cause(err)
 
134
        switch cause {
 
135
        case nil:
 
136
                return true, nil
 
137
        case lease.ErrClaimDenied:
 
138
                return false, nil
 
139
        }
 
140
        return false, errors.Trace(err)
 
141
}
 
142
 
 
143
// sleep waits for half the duration of a (presumed) earlier successful claim.
 
144
func sleep(config FlagConfig) <-chan time.Time {
 
145
        return config.Clock.After(config.Duration / 2)
 
146
}
 
147
 
 
148
// wait is a runFunc that ignores its abort chan and always returns an error;
 
149
// either because of a failed api call, or a successful one, which indicates
 
150
// that no lease is held; hence, that the worker should be bounced.
 
151
func waitVacant(config FlagConfig, _ <-chan struct{}) error {
 
152
        if err := config.Facade.Wait(); err != nil {
 
153
                return errors.Trace(err)
 
154
        }
 
155
        return ErrRefresh
 
156
}