/
deployer.go
199 lines (180 loc) · 5.71 KB
/
deployer.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package deployer
import (
"fmt"
"github.com/juju/errors"
"github.com/juju/loggo"
"github.com/juju/names"
"github.com/juju/utils"
"github.com/juju/utils/set"
"github.com/juju/juju/agent"
apideployer "github.com/juju/juju/api/deployer"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/watcher"
"github.com/juju/juju/worker"
)
var logger = loggo.GetLogger("juju.worker.deployer")
// Deployer is responsible for deploying and recalling unit agents, according
// to changes in a set of state units; and for the final removal of its agents'
// units from state when they are no longer needed.
type Deployer struct {
st *apideployer.State
ctx Context
deployed set.Strings
}
// Context abstracts away the differences between different unit deployment
// strategies; where a Deployer is responsible for what to deploy, a Context
// is responsible for how to deploy.
type Context interface {
// DeployUnit causes the agent for the specified unit to be started and run
// continuously until further notice without further intervention. It will
// return an error if the agent is already deployed.
DeployUnit(unitName, initialPassword string) error
// RecallUnit causes the agent for the specified unit to be stopped, and
// the agent's data to be destroyed. It will return an error if the agent
// was not deployed by the manager.
RecallUnit(unitName string) error
// DeployedUnits returns the names of all units deployed by the manager.
DeployedUnits() ([]string, error)
// AgentConfig returns the agent config for the machine agent that is
// running the deployer.
AgentConfig() agent.Config
}
// NewDeployer returns a Worker that deploys and recalls unit agents
// via ctx, taking a machine id to operate on.
func NewDeployer(st *apideployer.State, ctx Context) (worker.Worker, error) {
d := &Deployer{
st: st,
ctx: ctx,
deployed: make(set.Strings),
}
w, err := watcher.NewStringsWorker(watcher.StringsConfig{
Handler: d,
})
if err != nil {
return nil, errors.Trace(err)
}
return w, nil
}
func (d *Deployer) SetUp() (watcher.StringsWatcher, error) {
tag := d.ctx.AgentConfig().Tag()
machineTag, ok := tag.(names.MachineTag)
if !ok {
return nil, errors.Errorf("expected names.MachineTag, got %T", tag)
}
machine, err := d.st.Machine(machineTag)
if err != nil {
return nil, err
}
machineUnitsWatcher, err := machine.WatchUnits()
if err != nil {
return nil, err
}
deployed, err := d.ctx.DeployedUnits()
if err != nil {
return nil, err
}
for _, unitName := range deployed {
d.deployed.Add(unitName)
if err := d.changed(unitName); err != nil {
return nil, err
}
}
return machineUnitsWatcher, nil
}
func (d *Deployer) Handle(_ <-chan struct{}, unitNames []string) error {
for _, unitName := range unitNames {
if err := d.changed(unitName); err != nil {
return err
}
}
return nil
}
// changed ensures that the named unit is deployed, recalled, or removed, as
// indicated by its state.
func (d *Deployer) changed(unitName string) error {
unitTag := names.NewUnitTag(unitName)
// Determine unit life state, and whether we're responsible for it.
logger.Infof("checking unit %q", unitName)
var life params.Life
unit, err := d.st.Unit(unitTag)
if params.IsCodeNotFoundOrCodeUnauthorized(err) {
life = params.Dead
} else if err != nil {
return err
} else {
life = unit.Life()
}
// Deployed units must be removed if they're Dead, or if the deployer
// is no longer responsible for them.
if d.deployed.Contains(unitName) {
if life == params.Dead {
if err := d.recall(unitName); err != nil {
return err
}
}
}
// The only units that should be deployed are those that (1) we are responsible
// for and (2) are Alive -- if we're responsible for a Dying unit that is not
// yet deployed, we should remove it immediately rather than undergo the hassle
// of deploying a unit agent purely so it can set itself to Dead.
if !d.deployed.Contains(unitName) {
if life == params.Alive {
return d.deploy(unit)
} else if unit != nil {
return d.remove(unit)
}
}
return nil
}
// deploy will deploy the supplied unit with the deployer's manager. It will
// panic if it observes inconsistent internal state.
func (d *Deployer) deploy(unit *apideployer.Unit) error {
unitName := unit.Name()
if d.deployed.Contains(unit.Name()) {
panic("must not re-deploy a deployed unit")
}
logger.Infof("deploying unit %q", unitName)
initialPassword, err := utils.RandomPassword()
if err != nil {
return err
}
if err := unit.SetPassword(initialPassword); err != nil {
return fmt.Errorf("cannot set password for unit %q: %v", unitName, err)
}
if err := d.ctx.DeployUnit(unitName, initialPassword); err != nil {
return err
}
d.deployed.Add(unitName)
return nil
}
// recall will recall the named unit with the deployer's manager. It will
// panic if it observes inconsistent internal state.
func (d *Deployer) recall(unitName string) error {
if !d.deployed.Contains(unitName) {
panic("must not recall a unit that is not deployed")
}
logger.Infof("recalling unit %q", unitName)
if err := d.ctx.RecallUnit(unitName); err != nil {
return err
}
d.deployed.Remove(unitName)
return nil
}
// remove will remove the supplied unit from state. It will panic if it
// observes inconsistent internal state.
func (d *Deployer) remove(unit *apideployer.Unit) error {
unitName := unit.Name()
if d.deployed.Contains(unitName) {
panic("must not remove a deployed unit")
} else if unit.Life() == params.Alive {
panic("must not remove an Alive unit")
}
logger.Infof("removing unit %q", unitName)
return unit.Remove()
}
func (d *Deployer) TearDown() error {
// Nothing to do here.
return nil
}