-
Notifications
You must be signed in to change notification settings - Fork 491
/
leader.go
113 lines (99 loc) · 3.37 KB
/
leader.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package context
import (
"github.com/juju/errors"
"github.com/juju/juju/core/leadership"
)
var (
errIsMinion = errors.New("not the leader")
)
// LeadershipSettingsAccessor is an interface that allows us not to have
// to use the concrete `api/uniter/LeadershipSettingsAccessor` type, thus
// simplifying testing.
type LeadershipSettingsAccessor interface {
Read(applicationName string) (map[string]string, error)
Merge(applicationName, unitName string, settings map[string]string) error
}
// LeadershipContext provides several hooks.Context methods. It
// exists separately of HookContext for clarity, and ease of testing.
type LeadershipContext interface {
IsLeader() (bool, error)
LeaderSettings() (map[string]string, error)
WriteLeaderSettings(map[string]string) error
}
type leadershipContext struct {
accessor LeadershipSettingsAccessor
tracker leadership.Tracker
applicationName string
unitName string
isMinion bool
settings map[string]string
}
func NewLeadershipContext(accessor LeadershipSettingsAccessor, tracker leadership.Tracker, unitName string) LeadershipContext {
return &leadershipContext{
accessor: accessor,
tracker: tracker,
applicationName: tracker.ApplicationName(),
unitName: unitName,
}
}
// newLeadershipContext allows us to swap out the leadership context creator for
// factory tests.
var newLeadershipContext = NewLeadershipContext
// IsLeader is part of the hooks.Context interface.
func (ctx *leadershipContext) IsLeader() (bool, error) {
// This doesn't technically need an error return, but that feels like a
// happy accident of the current implementation and not a reason to change
// the interface we're implementing.
err := ctx.ensureLeader()
switch err {
case nil:
return true, nil
case errIsMinion:
return false, nil
}
return false, errors.Trace(err)
}
// WriteLeaderSettings is part of the hooks.Context interface.
func (ctx *leadershipContext) WriteLeaderSettings(settings map[string]string) error {
// This may trigger a lease refresh; it would be desirable to use a less
// eager approach here, but we're working around a race described in
// `apiserver/leadership.LeadershipSettingsAccessor.Merge`, and as of
// 2015-02-19 it's better to stay eager.
err := ctx.ensureLeader()
if err == nil {
// Clear local settings; if we need them again we should use the values
// as merged by the server. But we don't need to get them again right now;
// the charm may not need to ask again before the hook finishes.
ctx.settings = nil
err = ctx.accessor.Merge(ctx.applicationName, ctx.unitName, settings)
}
return errors.Annotate(err, "cannot write settings")
}
// LeaderSettings is part of the hooks.Context interface.
func (ctx *leadershipContext) LeaderSettings() (map[string]string, error) {
if ctx.settings == nil {
var err error
ctx.settings, err = ctx.accessor.Read(ctx.applicationName)
if err != nil {
return nil, errors.Annotate(err, "cannot read settings")
}
}
result := map[string]string{}
for key, value := range ctx.settings {
result[key] = value
}
return result, nil
}
func (ctx *leadershipContext) ensureLeader() error {
if ctx.isMinion {
return errIsMinion
}
success := ctx.tracker.ClaimLeader().Wait()
if !success {
ctx.isMinion = true
return errIsMinion
}
return nil
}