Merge lp:~wallyworld/juju-core/agent-conf-upgradedto into lp:~go-bot/juju-core/trunk
- agent-conf-upgradedto
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ian Booth |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2359 |
Proposed branch: | lp:~wallyworld/juju-core/agent-conf-upgradedto |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
948 lines (+338/-206) 14 files modified
agent/agent.go (+71/-41) agent/agent_test.go (+122/-78) agent/bootstrap_test.go (+12/-10) agent/format-1.12_whitebox_test.go (+3/-0) agent/format-1.16.go (+35/-18) agent/format-1.16_whitebox_test.go (+25/-1) agent/format_whitebox_test.go (+8/-6) cmd/jujud/agent_test.go (+16/-14) cmd/jujud/bootstrap_test.go (+9/-7) environs/cloudinit/cloudinit.go (+10/-8) juju/testing/conn.go (+8/-7) worker/deployer/simple.go (+5/-4) worker/provisioner/kvm-broker_test.go (+7/-6) worker/provisioner/lxc-broker_test.go (+7/-6) |
To merge this branch: | bzr merge lp:~wallyworld/juju-core/agent-conf-upgradedto |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Tim Penhey (community) | Approve | ||
Review via email: mp+207600@code.launchpad.net |
Commit message
Record upgradedToVersion in agent conf
The agent config files now record the version for
which upgrade steps were run. If there is no entry
in a config file, is is assumed the version is 1.16.
So there is no need for any migration, nor are there
compatibility issues. When 1.18 is deployed, the upgrade
steps are run and the config file will have 1.18 written
to it, allowing 1.20 to know that it is upgrding from
1.18 etc.
Description of the change
Record upgradedToVersion in agent conf
The agent config files now record the version for
which upgrade steps were run. If there is no entry
in a config file, is is assumed the version is 1.16.
So there is no need for any migration, nor are there
compatibility issues. When 1.18 is deployed, the upgrade
steps are run and the config file will have 1.18 written
to it, allowing 1.20 to know that it is upgrding from
1.18 etc.
Ian Booth (wallyworld) wrote : | # |
Tim Penhey (thumper) wrote : | # |
https:/
File agent/agent.go (right):
https:/
agent/agent.go:412: return c.Write()
If c.Write fails we now have the wrong version stored locally.
Care to fix that?
https:/
File agent/agent_test.go (right):
https:/
agent/agent_
these variable names no longer make sense.
https:/
File agent/format-
https:/
agent/format-
I find this much more complicated to follow than a version that doesn't
have named params:
func (*formatter_1_16) upgradedToVersi
error) {
if value != "" {
return version.
}
return version.
}
Ian Booth (wallyworld) wrote : | # |
Please take a look.
https:/
File agent/agent_test.go (right):
https:/
agent/agent_
On 2014/02/26 02:23:41, thumper wrote:
> these variable names no longer make sense.
Done.
https:/
File agent/format-
https:/
agent/format-
On 2014/02/26 02:23:41, thumper wrote:
> I find this much more complicated to follow than a version that
doesn't have
> named params:
> func (*formatter_1_16) upgradedToVersi
(version.Number, error) {
> if value != "" {
> return version.
> }
> return version.
> }
Done.
Ian Booth (wallyworld) wrote : | # |
https:/
File agent/agent.go (right):
https:/
agent/agent.go:412: return c.Write()
On 2014/02/26 02:23:41, thumper wrote:
> If c.Write fails we now have the wrong version stored locally.
> Care to fix that?
Done.
Tim Penhey (thumper) wrote : | # |
Tim Penhey (thumper) : | # |
Go Bot (go-bot) wrote : | # |
The attempt to merge lp:~wallyworld/juju-core/agent-conf-upgradedto into lp:juju-core failed. Below is the output from the failed tests.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
? launchpad.
? launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
ok launchpad.
ok launchpad.
? launchpad.
Preview Diff
1 | === modified file 'agent/agent.go' |
2 | --- agent/agent.go 2014-02-18 05:43:06 +0000 |
3 | +++ agent/agent.go 2014-02-26 03:43:42 +0000 |
4 | @@ -17,6 +17,7 @@ |
5 | "launchpad.net/juju-core/state/api" |
6 | "launchpad.net/juju-core/state/api/params" |
7 | "launchpad.net/juju-core/utils" |
8 | + "launchpad.net/juju-core/version" |
9 | ) |
10 | |
11 | var logger = loggo.GetLogger("juju.agent") |
12 | @@ -88,6 +89,14 @@ |
13 | // APIServerDetails returns the details needed to run an API server. |
14 | APIServerDetails() (port int, cert, key []byte) |
15 | |
16 | + // UpgradedToVersion returns the version for which all upgrade steps have been |
17 | + // successfully run, which is also the same as the initially deployed version. |
18 | + UpgradedToVersion() version.Number |
19 | + |
20 | + // WriteUpgradedToVersion updates the config's UpgradedToVersion and writes |
21 | + // the new agent configuration. |
22 | + WriteUpgradedToVersion(newVersion version.Number) error |
23 | + |
24 | // Value returns the value associated with the key, or an empty string if |
25 | // the key is not found. |
26 | Value(key string) string |
27 | @@ -122,63 +131,69 @@ |
28 | } |
29 | |
30 | type configInternal struct { |
31 | - dataDir string |
32 | - tag string |
33 | - nonce string |
34 | - caCert []byte |
35 | - stateDetails *connectionDetails |
36 | - apiDetails *connectionDetails |
37 | - oldPassword string |
38 | - stateServerCert []byte |
39 | - stateServerKey []byte |
40 | - apiPort int |
41 | - values map[string]string |
42 | + dataDir string |
43 | + tag string |
44 | + upgradedToVersion version.Number |
45 | + nonce string |
46 | + caCert []byte |
47 | + stateDetails *connectionDetails |
48 | + apiDetails *connectionDetails |
49 | + oldPassword string |
50 | + stateServerCert []byte |
51 | + stateServerKey []byte |
52 | + apiPort int |
53 | + values map[string]string |
54 | } |
55 | |
56 | type AgentConfigParams struct { |
57 | - DataDir string |
58 | - Tag string |
59 | - Password string |
60 | - Nonce string |
61 | - StateAddresses []string |
62 | - APIAddresses []string |
63 | - CACert []byte |
64 | - Values map[string]string |
65 | + DataDir string |
66 | + Tag string |
67 | + UpgradedToVersion version.Number |
68 | + Password string |
69 | + Nonce string |
70 | + StateAddresses []string |
71 | + APIAddresses []string |
72 | + CACert []byte |
73 | + Values map[string]string |
74 | } |
75 | |
76 | // NewAgentConfig returns a new config object suitable for use for a |
77 | // machine or unit agent. |
78 | -func NewAgentConfig(params AgentConfigParams) (Config, error) { |
79 | - if params.DataDir == "" { |
80 | +func NewAgentConfig(configParams AgentConfigParams) (Config, error) { |
81 | + if configParams.DataDir == "" { |
82 | return nil, errgo.Trace(requiredError("data directory")) |
83 | } |
84 | - if params.Tag == "" { |
85 | + if configParams.Tag == "" { |
86 | return nil, errgo.Trace(requiredError("entity tag")) |
87 | } |
88 | - if params.Password == "" { |
89 | + if configParams.UpgradedToVersion == version.Zero { |
90 | + return nil, errgo.Trace(requiredError("upgradedToVersion")) |
91 | + } |
92 | + if configParams.Password == "" { |
93 | return nil, errgo.Trace(requiredError("password")) |
94 | } |
95 | - if params.CACert == nil { |
96 | + if configParams.CACert == nil { |
97 | return nil, errgo.Trace(requiredError("CA certificate")) |
98 | } |
99 | // Note that the password parts of the state and api information are |
100 | // blank. This is by design. |
101 | config := &configInternal{ |
102 | - dataDir: params.DataDir, |
103 | - tag: params.Tag, |
104 | - nonce: params.Nonce, |
105 | - caCert: params.CACert, |
106 | - oldPassword: params.Password, |
107 | - values: params.Values, |
108 | + dataDir: configParams.DataDir, |
109 | + tag: configParams.Tag, |
110 | + upgradedToVersion: configParams.UpgradedToVersion, |
111 | + nonce: configParams.Nonce, |
112 | + caCert: configParams.CACert, |
113 | + oldPassword: configParams.Password, |
114 | + values: configParams.Values, |
115 | } |
116 | - if len(params.StateAddresses) > 0 { |
117 | + if len(configParams.StateAddresses) > 0 { |
118 | config.stateDetails = &connectionDetails{ |
119 | - addresses: params.StateAddresses, |
120 | + addresses: configParams.StateAddresses, |
121 | } |
122 | } |
123 | - if len(params.APIAddresses) > 0 { |
124 | + if len(configParams.APIAddresses) > 0 { |
125 | config.apiDetails = &connectionDetails{ |
126 | - addresses: params.APIAddresses, |
127 | + addresses: configParams.APIAddresses, |
128 | } |
129 | } |
130 | if err := config.check(); err != nil { |
131 | @@ -200,21 +215,21 @@ |
132 | |
133 | // NewStateMachineConfig returns a configuration suitable for |
134 | // a machine running the state server. |
135 | -func NewStateMachineConfig(params StateMachineConfigParams) (Config, error) { |
136 | - if params.StateServerCert == nil { |
137 | +func NewStateMachineConfig(configParams StateMachineConfigParams) (Config, error) { |
138 | + if configParams.StateServerCert == nil { |
139 | return nil, errgo.Trace(requiredError("state server cert")) |
140 | } |
141 | - if params.StateServerKey == nil { |
142 | + if configParams.StateServerKey == nil { |
143 | return nil, errgo.Trace(requiredError("state server key")) |
144 | } |
145 | - config0, err := NewAgentConfig(params.AgentConfigParams) |
146 | + config0, err := NewAgentConfig(configParams.AgentConfigParams) |
147 | if err != nil { |
148 | return nil, err |
149 | } |
150 | config := config0.(*configInternal) |
151 | - config.stateServerCert = params.StateServerCert |
152 | - config.stateServerKey = params.StateServerKey |
153 | - config.apiPort = params.APIPort |
154 | + config.stateServerCert = configParams.StateServerCert |
155 | + config.stateServerKey = configParams.StateServerKey |
156 | + config.apiPort = configParams.APIPort |
157 | return config, nil |
158 | } |
159 | |
160 | @@ -279,6 +294,10 @@ |
161 | return c.nonce |
162 | } |
163 | |
164 | +func (c *configInternal) UpgradedToVersion() version.Number { |
165 | + return c.upgradedToVersion |
166 | +} |
167 | + |
168 | func (c *configInternal) CACert() []byte { |
169 | // Give the caller their own copy of the cert to avoid any possibility of |
170 | // modifying the config's copy. |
171 | @@ -388,6 +407,17 @@ |
172 | return currentFormatter.write(c) |
173 | } |
174 | |
175 | +func (c *configInternal) WriteUpgradedToVersion(newVersion version.Number) error { |
176 | + originalVersion := c.upgradedToVersion |
177 | + c.upgradedToVersion = newVersion |
178 | + err := c.Write() |
179 | + if err != nil { |
180 | + // We don't want to retain the new version if there's been an error writing the file. |
181 | + c.upgradedToVersion = originalVersion |
182 | + } |
183 | + return err |
184 | +} |
185 | + |
186 | func (c *configInternal) WriteCommands() ([]string, error) { |
187 | return currentFormatter.writeCommands(c) |
188 | } |
189 | |
190 | === modified file 'agent/agent_test.go' |
191 | --- agent/agent_test.go 2013-11-19 13:23:51 +0000 |
192 | +++ agent/agent_test.go 2014-02-26 03:43:42 +0000 |
193 | @@ -8,6 +8,7 @@ |
194 | |
195 | "launchpad.net/juju-core/agent" |
196 | "launchpad.net/juju-core/testing/testbase" |
197 | + "launchpad.net/juju-core/version" |
198 | ) |
199 | |
200 | type suite struct { |
201 | @@ -30,87 +31,103 @@ |
202 | }, |
203 | checkErr: "entity tag not found in configuration", |
204 | }, { |
205 | + about: "missing upgraded to version", |
206 | + params: agent.AgentConfigParams{ |
207 | + DataDir: "/data/dir", |
208 | + Tag: "omg", |
209 | + }, |
210 | + checkErr: "upgradedToVersion not found in configuration", |
211 | +}, { |
212 | about: "missing password", |
213 | params: agent.AgentConfigParams{ |
214 | - DataDir: "/data/dir", |
215 | - Tag: "omg", |
216 | + DataDir: "/data/dir", |
217 | + Tag: "omg", |
218 | + UpgradedToVersion: version.Current.Number, |
219 | }, |
220 | checkErr: "password not found in configuration", |
221 | }, { |
222 | about: "missing CA cert", |
223 | params: agent.AgentConfigParams{ |
224 | - DataDir: "/data/dir", |
225 | - Tag: "omg", |
226 | - Password: "sekrit", |
227 | + DataDir: "/data/dir", |
228 | + Tag: "omg", |
229 | + UpgradedToVersion: version.Current.Number, |
230 | + Password: "sekrit", |
231 | }, |
232 | checkErr: "CA certificate not found in configuration", |
233 | }, { |
234 | about: "need either state or api addresses", |
235 | params: agent.AgentConfigParams{ |
236 | - DataDir: "/data/dir", |
237 | - Tag: "omg", |
238 | - Password: "sekrit", |
239 | - CACert: []byte("ca cert"), |
240 | + DataDir: "/data/dir", |
241 | + Tag: "omg", |
242 | + UpgradedToVersion: version.Current.Number, |
243 | + Password: "sekrit", |
244 | + CACert: []byte("ca cert"), |
245 | }, |
246 | checkErr: "state or API addresses not found in configuration", |
247 | }, { |
248 | about: "invalid state address", |
249 | params: agent.AgentConfigParams{ |
250 | - DataDir: "/data/dir", |
251 | - Tag: "omg", |
252 | - Password: "sekrit", |
253 | - CACert: []byte("ca cert"), |
254 | - StateAddresses: []string{"localhost:8080", "bad-address"}, |
255 | + DataDir: "/data/dir", |
256 | + Tag: "omg", |
257 | + UpgradedToVersion: version.Current.Number, |
258 | + Password: "sekrit", |
259 | + CACert: []byte("ca cert"), |
260 | + StateAddresses: []string{"localhost:8080", "bad-address"}, |
261 | }, |
262 | checkErr: `invalid state server address "bad-address"`, |
263 | }, { |
264 | about: "invalid api address", |
265 | params: agent.AgentConfigParams{ |
266 | - DataDir: "/data/dir", |
267 | - Tag: "omg", |
268 | - Password: "sekrit", |
269 | - CACert: []byte("ca cert"), |
270 | - APIAddresses: []string{"localhost:8080", "bad-address"}, |
271 | + DataDir: "/data/dir", |
272 | + Tag: "omg", |
273 | + UpgradedToVersion: version.Current.Number, |
274 | + Password: "sekrit", |
275 | + CACert: []byte("ca cert"), |
276 | + APIAddresses: []string{"localhost:8080", "bad-address"}, |
277 | }, |
278 | checkErr: `invalid API server address "bad-address"`, |
279 | }, { |
280 | about: "good state addresses", |
281 | params: agent.AgentConfigParams{ |
282 | - DataDir: "/data/dir", |
283 | - Tag: "omg", |
284 | - Password: "sekrit", |
285 | - CACert: []byte("ca cert"), |
286 | - StateAddresses: []string{"localhost:1234"}, |
287 | + DataDir: "/data/dir", |
288 | + Tag: "omg", |
289 | + UpgradedToVersion: version.Current.Number, |
290 | + Password: "sekrit", |
291 | + CACert: []byte("ca cert"), |
292 | + StateAddresses: []string{"localhost:1234"}, |
293 | }, |
294 | }, { |
295 | about: "good api addresses", |
296 | params: agent.AgentConfigParams{ |
297 | - DataDir: "/data/dir", |
298 | - Tag: "omg", |
299 | - Password: "sekrit", |
300 | - CACert: []byte("ca cert"), |
301 | - APIAddresses: []string{"localhost:1234"}, |
302 | + DataDir: "/data/dir", |
303 | + Tag: "omg", |
304 | + UpgradedToVersion: version.Current.Number, |
305 | + Password: "sekrit", |
306 | + CACert: []byte("ca cert"), |
307 | + APIAddresses: []string{"localhost:1234"}, |
308 | }, |
309 | }, { |
310 | about: "both state and api addresses", |
311 | params: agent.AgentConfigParams{ |
312 | - DataDir: "/data/dir", |
313 | - Tag: "omg", |
314 | - Password: "sekrit", |
315 | - CACert: []byte("ca cert"), |
316 | - StateAddresses: []string{"localhost:1234"}, |
317 | - APIAddresses: []string{"localhost:1235"}, |
318 | + DataDir: "/data/dir", |
319 | + Tag: "omg", |
320 | + UpgradedToVersion: version.Current.Number, |
321 | + Password: "sekrit", |
322 | + CACert: []byte("ca cert"), |
323 | + StateAddresses: []string{"localhost:1234"}, |
324 | + APIAddresses: []string{"localhost:1235"}, |
325 | }, |
326 | }, { |
327 | about: "everything...", |
328 | params: agent.AgentConfigParams{ |
329 | - DataDir: "/data/dir", |
330 | - Tag: "omg", |
331 | - Password: "sekrit", |
332 | - CACert: []byte("ca cert"), |
333 | - StateAddresses: []string{"localhost:1234"}, |
334 | - APIAddresses: []string{"localhost:1235"}, |
335 | - Nonce: "a nonce", |
336 | + DataDir: "/data/dir", |
337 | + Tag: "omg", |
338 | + Password: "sekrit", |
339 | + UpgradedToVersion: version.Current.Number, |
340 | + CACert: []byte("ca cert"), |
341 | + StateAddresses: []string{"localhost:1234"}, |
342 | + APIAddresses: []string{"localhost:1235"}, |
343 | + Nonce: "a nonce", |
344 | }, |
345 | }} |
346 | |
347 | @@ -168,13 +185,14 @@ |
348 | } |
349 | |
350 | var attributeParams = agent.AgentConfigParams{ |
351 | - DataDir: "/data/dir", |
352 | - Tag: "omg", |
353 | - Password: "sekrit", |
354 | - CACert: []byte("ca cert"), |
355 | - StateAddresses: []string{"localhost:1234"}, |
356 | - APIAddresses: []string{"localhost:1235"}, |
357 | - Nonce: "a nonce", |
358 | + DataDir: "/data/dir", |
359 | + Tag: "omg", |
360 | + UpgradedToVersion: version.Current.Number, |
361 | + Password: "sekrit", |
362 | + CACert: []byte("ca cert"), |
363 | + StateAddresses: []string{"localhost:1234"}, |
364 | + APIAddresses: []string{"localhost:1235"}, |
365 | + Nonce: "a nonce", |
366 | } |
367 | |
368 | func (*suite) TestAttributes(c *gc.C) { |
369 | @@ -184,6 +202,7 @@ |
370 | c.Assert(conf.Tag(), gc.Equals, "omg") |
371 | c.Assert(conf.Dir(), gc.Equals, "/data/dir/agents/omg") |
372 | c.Assert(conf.Nonce(), gc.Equals, "a nonce") |
373 | + c.Assert(conf.UpgradedToVersion(), gc.DeepEquals, version.Current.Number) |
374 | } |
375 | |
376 | func (s *suite) TestApiAddressesCantWriteBack(c *gc.C) { |
377 | @@ -198,22 +217,27 @@ |
378 | c.Assert(err, gc.IsNil) |
379 | c.Assert(newValue, gc.DeepEquals, []string{"localhost:1235"}) |
380 | } |
381 | -func (*suite) TestWriteAndRead(c *gc.C) { |
382 | - testParams := attributeParams |
383 | - testParams.DataDir = c.MkDir() |
384 | - conf, err := agent.NewAgentConfig(testParams) |
385 | - c.Assert(err, gc.IsNil) |
386 | |
387 | - c.Assert(conf.Write(), gc.IsNil) |
388 | - reread, err := agent.ReadConf(conf.DataDir(), conf.Tag()) |
389 | - c.Assert(err, gc.IsNil) |
390 | +func assertConfigEqual(c *gc.C, c1, c2 agent.Config) { |
391 | // Since we can't directly poke the internals, we'll use the WriteCommands |
392 | // method. |
393 | - confCommands, err := conf.WriteCommands() |
394 | - c.Assert(err, gc.IsNil) |
395 | - rereadCommands, err := reread.WriteCommands() |
396 | - c.Assert(err, gc.IsNil) |
397 | - c.Assert(confCommands, gc.DeepEquals, rereadCommands) |
398 | + conf1Commands, err := c1.WriteCommands() |
399 | + c.Assert(err, gc.IsNil) |
400 | + conf2Commands, err := c2.WriteCommands() |
401 | + c.Assert(err, gc.IsNil) |
402 | + c.Assert(conf1Commands, gc.DeepEquals, conf2Commands) |
403 | +} |
404 | + |
405 | +func (*suite) TestWriteAndRead(c *gc.C) { |
406 | + testParams := attributeParams |
407 | + testParams.DataDir = c.MkDir() |
408 | + conf, err := agent.NewAgentConfig(testParams) |
409 | + c.Assert(err, gc.IsNil) |
410 | + |
411 | + c.Assert(conf.Write(), gc.IsNil) |
412 | + reread, err := agent.ReadConf(conf.DataDir(), conf.Tag()) |
413 | + c.Assert(err, gc.IsNil) |
414 | + assertConfigEqual(c, conf, reread) |
415 | } |
416 | |
417 | func (*suite) TestWriteNewPassword(c *gc.C) { |
418 | @@ -224,30 +248,33 @@ |
419 | }{{ |
420 | about: "good state addresses", |
421 | params: agent.AgentConfigParams{ |
422 | - DataDir: c.MkDir(), |
423 | - Tag: "omg", |
424 | - Password: "sekrit", |
425 | - CACert: []byte("ca cert"), |
426 | - StateAddresses: []string{"localhost:1234"}, |
427 | + DataDir: c.MkDir(), |
428 | + Tag: "omg", |
429 | + UpgradedToVersion: version.Current.Number, |
430 | + Password: "sekrit", |
431 | + CACert: []byte("ca cert"), |
432 | + StateAddresses: []string{"localhost:1234"}, |
433 | }, |
434 | }, { |
435 | about: "good api addresses", |
436 | params: agent.AgentConfigParams{ |
437 | - DataDir: c.MkDir(), |
438 | - Tag: "omg", |
439 | - Password: "sekrit", |
440 | - CACert: []byte("ca cert"), |
441 | - APIAddresses: []string{"localhost:1234"}, |
442 | + DataDir: c.MkDir(), |
443 | + Tag: "omg", |
444 | + UpgradedToVersion: version.Current.Number, |
445 | + Password: "sekrit", |
446 | + CACert: []byte("ca cert"), |
447 | + APIAddresses: []string{"localhost:1234"}, |
448 | }, |
449 | }, { |
450 | about: "both state and api addresses", |
451 | params: agent.AgentConfigParams{ |
452 | - DataDir: c.MkDir(), |
453 | - Tag: "omg", |
454 | - Password: "sekrit", |
455 | - CACert: []byte("ca cert"), |
456 | - StateAddresses: []string{"localhost:1234"}, |
457 | - APIAddresses: []string{"localhost:1235"}, |
458 | + DataDir: c.MkDir(), |
459 | + Tag: "omg", |
460 | + UpgradedToVersion: version.Current.Number, |
461 | + Password: "sekrit", |
462 | + CACert: []byte("ca cert"), |
463 | + StateAddresses: []string{"localhost:1234"}, |
464 | + APIAddresses: []string{"localhost:1235"}, |
465 | }, |
466 | }} { |
467 | c.Logf("%v: %s", i, test.about) |
468 | @@ -263,6 +290,23 @@ |
469 | } |
470 | } |
471 | |
472 | +func (*suite) TestWriteUpgradedToVersion(c *gc.C) { |
473 | + testParams := attributeParams |
474 | + testParams.DataDir = c.MkDir() |
475 | + conf, err := agent.NewAgentConfig(testParams) |
476 | + c.Assert(err, gc.IsNil) |
477 | + c.Assert(conf.Write(), gc.IsNil) |
478 | + |
479 | + newVersion := version.Current.Number |
480 | + newVersion.Major++ |
481 | + c.Assert(conf.WriteUpgradedToVersion(newVersion), gc.IsNil) |
482 | + c.Assert(conf.UpgradedToVersion(), gc.DeepEquals, newVersion) |
483 | + |
484 | + // Show that the upgradedToVersion is saved. |
485 | + reread, err := agent.ReadConf(conf.DataDir(), conf.Tag()) |
486 | + assertConfigEqual(c, conf, reread) |
487 | +} |
488 | + |
489 | // Actual opening of state and api requires a lot more boiler plate to make |
490 | // sure they are valid connections. This is done in the cmd/jujud tests for |
491 | // bootstrap, machine and unit tests. |
492 | |
493 | === modified file 'agent/bootstrap_test.go' |
494 | --- agent/bootstrap_test.go 2014-02-19 06:50:24 +0000 |
495 | +++ agent/bootstrap_test.go 2014-02-26 03:43:42 +0000 |
496 | @@ -51,11 +51,12 @@ |
497 | |
498 | pwHash := utils.UserPasswordHash(testing.DefaultMongoPassword, utils.CompatSalt) |
499 | cfg, err := agent.NewAgentConfig(agent.AgentConfigParams{ |
500 | - DataDir: dataDir, |
501 | - Tag: "machine-0", |
502 | - StateAddresses: []string{testing.MgoServer.Addr()}, |
503 | - CACert: []byte(testing.CACert), |
504 | - Password: pwHash, |
505 | + DataDir: dataDir, |
506 | + Tag: "machine-0", |
507 | + UpgradedToVersion: version.Current.Number, |
508 | + StateAddresses: []string{testing.MgoServer.Addr()}, |
509 | + CACert: []byte(testing.CACert), |
510 | + Password: pwHash, |
511 | }) |
512 | c.Assert(err, gc.IsNil) |
513 | expectConstraints := constraints.MustParse("mem=1024M") |
514 | @@ -116,11 +117,12 @@ |
515 | dataDir := c.MkDir() |
516 | pwHash := utils.UserPasswordHash(testing.DefaultMongoPassword, utils.CompatSalt) |
517 | cfg, err := agent.NewAgentConfig(agent.AgentConfigParams{ |
518 | - DataDir: dataDir, |
519 | - Tag: "machine-0", |
520 | - StateAddresses: []string{testing.MgoServer.Addr()}, |
521 | - CACert: []byte(testing.CACert), |
522 | - Password: pwHash, |
523 | + DataDir: dataDir, |
524 | + Tag: "machine-0", |
525 | + UpgradedToVersion: version.Current.Number, |
526 | + StateAddresses: []string{testing.MgoServer.Addr()}, |
527 | + CACert: []byte(testing.CACert), |
528 | + Password: pwHash, |
529 | }) |
530 | c.Assert(err, gc.IsNil) |
531 | expectConstraints := constraints.MustParse("mem=1024M") |
532 | |
533 | === modified file 'agent/format-1.12_whitebox_test.go' |
534 | --- agent/format-1.12_whitebox_test.go 2013-10-04 16:16:10 +0000 |
535 | +++ agent/format-1.12_whitebox_test.go 2014-02-26 03:43:42 +0000 |
536 | @@ -11,6 +11,7 @@ |
537 | |
538 | jc "launchpad.net/juju-core/testing/checkers" |
539 | "launchpad.net/juju-core/testing/testbase" |
540 | + "launchpad.net/juju-core/version" |
541 | ) |
542 | |
543 | type format_1_12Suite struct { |
544 | @@ -42,6 +43,8 @@ |
545 | } |
546 | |
547 | func (s *format_1_12Suite) assertWriteAndRead(c *gc.C, config *configInternal) { |
548 | + // Format 1.12 doesn't know about upgradedToVersion so zero it out. |
549 | + config.upgradedToVersion = version.Zero |
550 | err := s.formatter.write(config) |
551 | c.Assert(err, gc.IsNil) |
552 | // The readConfig is missing the dataDir initially. |
553 | |
554 | === modified file 'agent/format-1.16.go' |
555 | --- agent/format-1.16.go 2014-02-12 21:14:17 +0000 |
556 | +++ agent/format-1.16.go 2014-02-26 03:43:42 +0000 |
557 | @@ -12,6 +12,7 @@ |
558 | "launchpad.net/goyaml" |
559 | |
560 | "launchpad.net/juju-core/juju/osenv" |
561 | + "launchpad.net/juju-core/version" |
562 | ) |
563 | |
564 | const ( |
565 | @@ -29,8 +30,9 @@ |
566 | |
567 | // format_1_16Serialization holds information for a given agent. |
568 | type format_1_16Serialization struct { |
569 | - Tag string |
570 | - Nonce string |
571 | + Tag string |
572 | + Nonce string |
573 | + UpgradedToVersion string `yaml:"upgradedToVersion"` |
574 | // CACert is base64 encoded |
575 | CACert string |
576 | StateAddresses []string `yaml:",omitempty"` |
577 | @@ -64,6 +66,15 @@ |
578 | return |
579 | } |
580 | |
581 | +// upgradedToVersion parses the upgradedToVersion string value into a version.Number. |
582 | +// An empty value is returned as 1.16.0. |
583 | +func (*formatter_1_16) upgradedToVersion(value string) (version.Number, error) { |
584 | + if value != "" { |
585 | + return version.Parse(value) |
586 | + } |
587 | + return version.MustParse("1.16.0"), nil |
588 | +} |
589 | + |
590 | func (formatter *formatter_1_16) read(dirName string) (*configInternal, error) { |
591 | data, err := ioutil.ReadFile(formatter.configFile(dirName)) |
592 | if err != nil { |
593 | @@ -85,15 +96,20 @@ |
594 | if err != nil { |
595 | return nil, err |
596 | } |
597 | + upgradedToVersion, err := formatter.upgradedToVersion(format.UpgradedToVersion) |
598 | + if err != nil { |
599 | + return nil, err |
600 | + } |
601 | config := &configInternal{ |
602 | - tag: format.Tag, |
603 | - nonce: format.Nonce, |
604 | - caCert: caCert, |
605 | - oldPassword: format.OldPassword, |
606 | - stateServerCert: stateServerCert, |
607 | - stateServerKey: stateServerKey, |
608 | - apiPort: format.APIPort, |
609 | - values: format.Values, |
610 | + tag: format.Tag, |
611 | + nonce: format.Nonce, |
612 | + upgradedToVersion: upgradedToVersion, |
613 | + caCert: caCert, |
614 | + oldPassword: format.OldPassword, |
615 | + stateServerCert: stateServerCert, |
616 | + stateServerKey: stateServerKey, |
617 | + apiPort: format.APIPort, |
618 | + values: format.Values, |
619 | } |
620 | if len(format.StateAddresses) > 0 { |
621 | config.stateDetails = &connectionDetails{ |
622 | @@ -112,14 +128,15 @@ |
623 | |
624 | func (formatter *formatter_1_16) makeFormat(config *configInternal) *format_1_16Serialization { |
625 | format := &format_1_16Serialization{ |
626 | - Tag: config.tag, |
627 | - Nonce: config.nonce, |
628 | - CACert: base64.StdEncoding.EncodeToString(config.caCert), |
629 | - OldPassword: config.oldPassword, |
630 | - StateServerCert: base64.StdEncoding.EncodeToString(config.stateServerCert), |
631 | - StateServerKey: base64.StdEncoding.EncodeToString(config.stateServerKey), |
632 | - APIPort: config.apiPort, |
633 | - Values: config.values, |
634 | + Tag: config.tag, |
635 | + Nonce: config.nonce, |
636 | + UpgradedToVersion: config.upgradedToVersion.String(), |
637 | + CACert: base64.StdEncoding.EncodeToString(config.caCert), |
638 | + OldPassword: config.oldPassword, |
639 | + StateServerCert: base64.StdEncoding.EncodeToString(config.stateServerCert), |
640 | + StateServerKey: base64.StdEncoding.EncodeToString(config.stateServerKey), |
641 | + APIPort: config.apiPort, |
642 | + Values: config.values, |
643 | } |
644 | if config.stateDetails != nil { |
645 | format.StateAddresses = config.stateDetails.addresses |
646 | |
647 | === modified file 'agent/format-1.16_whitebox_test.go' |
648 | --- agent/format-1.16_whitebox_test.go 2014-02-12 21:14:17 +0000 |
649 | +++ agent/format-1.16_whitebox_test.go 2014-02-26 03:43:42 +0000 |
650 | @@ -8,14 +8,17 @@ |
651 | package agent |
652 | |
653 | import ( |
654 | + "io/ioutil" |
655 | "os" |
656 | "path" |
657 | + "path/filepath" |
658 | |
659 | gc "launchpad.net/gocheck" |
660 | |
661 | "launchpad.net/juju-core/juju/osenv" |
662 | jc "launchpad.net/juju-core/testing/checkers" |
663 | "launchpad.net/juju-core/testing/testbase" |
664 | + "launchpad.net/juju-core/version" |
665 | ) |
666 | |
667 | type format_1_16Suite struct { |
668 | @@ -48,6 +51,27 @@ |
669 | c.Assert(formatContent, gc.Equals, format_1_16) |
670 | } |
671 | |
672 | +var configDataWithoutUpgradedToVersion = ` |
673 | +tag: omg |
674 | +nonce: a nonce |
675 | +cacert: Y2EgY2VydA== |
676 | +stateaddresses: |
677 | +- localhost:1234 |
678 | +apiaddresses: |
679 | +- localhost:1235 |
680 | +oldpassword: sekrit |
681 | +values: {} |
682 | +` |
683 | + |
684 | +func (s *format_1_16Suite) TestMissingUpgradedToVersion(c *gc.C) { |
685 | + dataDir := c.MkDir() |
686 | + err := ioutil.WriteFile(filepath.Join(dataDir, "agent.conf"), []byte(configDataWithoutUpgradedToVersion), 0600) |
687 | + c.Assert(err, gc.IsNil) |
688 | + readConfig, err := s.formatter.read(dataDir) |
689 | + c.Assert(err, gc.IsNil) |
690 | + c.Assert(readConfig.UpgradedToVersion(), gc.Equals, version.MustParse("1.16.0")) |
691 | +} |
692 | + |
693 | func (s *format_1_16Suite) assertWriteAndRead(c *gc.C, config *configInternal) { |
694 | err := s.formatter.write(config) |
695 | c.Assert(err, gc.IsNil) |
696 | @@ -56,7 +80,7 @@ |
697 | c.Assert(err, gc.IsNil) |
698 | c.Assert(readConfig.dataDir, gc.Equals, "") |
699 | // This is put in by the ReadConf method that we are avoiding using |
700 | - // becuase it will have side-effects soon around migrating configs. |
701 | + // because it will have side-effects soon around migrating configs. |
702 | readConfig.dataDir = config.dataDir |
703 | c.Assert(readConfig, gc.DeepEquals, config) |
704 | } |
705 | |
706 | === modified file 'agent/format_whitebox_test.go' |
707 | --- agent/format_whitebox_test.go 2013-10-04 16:16:10 +0000 |
708 | +++ agent/format_whitebox_test.go 2014-02-26 03:43:42 +0000 |
709 | @@ -10,6 +10,7 @@ |
710 | gc "launchpad.net/gocheck" |
711 | |
712 | "launchpad.net/juju-core/testing/testbase" |
713 | + "launchpad.net/juju-core/version" |
714 | ) |
715 | |
716 | type formatSuite struct { |
717 | @@ -21,12 +22,13 @@ |
718 | // The agentParams are used by the specific formatter whitebox tests, and is |
719 | // located here for easy reuse. |
720 | var agentParams = AgentConfigParams{ |
721 | - Tag: "omg", |
722 | - Password: "sekrit", |
723 | - CACert: []byte("ca cert"), |
724 | - StateAddresses: []string{"localhost:1234"}, |
725 | - APIAddresses: []string{"localhost:1235"}, |
726 | - Nonce: "a nonce", |
727 | + Tag: "omg", |
728 | + UpgradedToVersion: version.Current.Number, |
729 | + Password: "sekrit", |
730 | + CACert: []byte("ca cert"), |
731 | + StateAddresses: []string{"localhost:1234"}, |
732 | + APIAddresses: []string{"localhost:1235"}, |
733 | + Nonce: "a nonce", |
734 | } |
735 | |
736 | func (*formatSuite) TestReadFormatEmptyDir(c *gc.C) { |
737 | |
738 | === modified file 'cmd/jujud/agent_test.go' |
739 | --- cmd/jujud/agent_test.go 2014-02-20 08:23:40 +0000 |
740 | +++ cmd/jujud/agent_test.go 2014-02-26 03:43:42 +0000 |
741 | @@ -207,13 +207,14 @@ |
742 | apiInfo := s.APIInfo(c) |
743 | conf, err := agent.NewAgentConfig( |
744 | agent.AgentConfigParams{ |
745 | - DataDir: s.DataDir(), |
746 | - Tag: tag, |
747 | - Password: password, |
748 | - Nonce: state.BootstrapNonce, |
749 | - StateAddresses: stateInfo.Addrs, |
750 | - APIAddresses: apiInfo.Addrs, |
751 | - CACert: stateInfo.CACert, |
752 | + DataDir: s.DataDir(), |
753 | + Tag: tag, |
754 | + UpgradedToVersion: version.Current.Number, |
755 | + Password: password, |
756 | + Nonce: state.BootstrapNonce, |
757 | + StateAddresses: stateInfo.Addrs, |
758 | + APIAddresses: apiInfo.Addrs, |
759 | + CACert: stateInfo.CACert, |
760 | }) |
761 | c.Assert(conf.Write(), gc.IsNil) |
762 | return conf, agentTools |
763 | @@ -226,13 +227,14 @@ |
764 | conf, err := agent.NewStateMachineConfig( |
765 | agent.StateMachineConfigParams{ |
766 | AgentConfigParams: agent.AgentConfigParams{ |
767 | - DataDir: dataDir, |
768 | - Tag: tag, |
769 | - Password: password, |
770 | - Nonce: state.BootstrapNonce, |
771 | - StateAddresses: stateInfo.Addrs, |
772 | - APIAddresses: apiAddr, |
773 | - CACert: stateInfo.CACert, |
774 | + DataDir: dataDir, |
775 | + Tag: tag, |
776 | + UpgradedToVersion: version.Current.Number, |
777 | + Password: password, |
778 | + Nonce: state.BootstrapNonce, |
779 | + StateAddresses: stateInfo.Addrs, |
780 | + APIAddresses: apiAddr, |
781 | + CACert: stateInfo.CACert, |
782 | }, |
783 | StateServerCert: []byte(coretesting.ServerCert), |
784 | StateServerKey: []byte(coretesting.ServerKey), |
785 | |
786 | === modified file 'cmd/jujud/bootstrap_test.go' |
787 | --- cmd/jujud/bootstrap_test.go 2014-02-19 06:31:52 +0000 |
788 | +++ cmd/jujud/bootstrap_test.go 2014-02-26 03:43:42 +0000 |
789 | @@ -24,6 +24,7 @@ |
790 | jc "launchpad.net/juju-core/testing/checkers" |
791 | "launchpad.net/juju-core/testing/testbase" |
792 | "launchpad.net/juju-core/utils" |
793 | + "launchpad.net/juju-core/version" |
794 | ) |
795 | |
796 | // We don't want to use JujuConnSuite because it gives us |
797 | @@ -85,13 +86,14 @@ |
798 | // NOTE: the old test used an equivalent of the NewAgentConfig, but it |
799 | // really should be using NewStateMachineConfig. |
800 | params := agent.AgentConfigParams{ |
801 | - DataDir: s.dataDir, |
802 | - Tag: "bootstrap", |
803 | - Password: testPasswordHash(), |
804 | - Nonce: state.BootstrapNonce, |
805 | - StateAddresses: []string{testing.MgoServer.Addr()}, |
806 | - APIAddresses: []string{"0.1.2.3:1234"}, |
807 | - CACert: []byte(testing.CACert), |
808 | + DataDir: s.dataDir, |
809 | + Tag: "bootstrap", |
810 | + UpgradedToVersion: version.Current.Number, |
811 | + Password: testPasswordHash(), |
812 | + Nonce: state.BootstrapNonce, |
813 | + StateAddresses: []string{testing.MgoServer.Addr()}, |
814 | + APIAddresses: []string{"0.1.2.3:1234"}, |
815 | + CACert: []byte(testing.CACert), |
816 | } |
817 | bootConf, err := agent.NewAgentConfig(params) |
818 | c.Assert(err, gc.IsNil) |
819 | |
820 | === modified file 'environs/cloudinit/cloudinit.go' |
821 | --- environs/cloudinit/cloudinit.go 2014-02-20 15:03:08 +0000 |
822 | +++ environs/cloudinit/cloudinit.go 2014-02-26 03:43:42 +0000 |
823 | @@ -27,6 +27,7 @@ |
824 | coretools "launchpad.net/juju-core/tools" |
825 | "launchpad.net/juju-core/upstart" |
826 | "launchpad.net/juju-core/utils" |
827 | + "launchpad.net/juju-core/version" |
828 | ) |
829 | |
830 | // BootstrapStateURLFile is used to communicate to the first bootstrap node |
831 | @@ -436,14 +437,15 @@ |
832 | password = cfg.StateInfo.Password |
833 | } |
834 | configParams := agent.AgentConfigParams{ |
835 | - DataDir: cfg.DataDir, |
836 | - Tag: tag, |
837 | - Password: password, |
838 | - Nonce: cfg.MachineNonce, |
839 | - StateAddresses: cfg.stateHostAddrs(), |
840 | - APIAddresses: cfg.apiHostAddrs(), |
841 | - CACert: cfg.StateInfo.CACert, |
842 | - Values: cfg.AgentEnvironment, |
843 | + DataDir: cfg.DataDir, |
844 | + Tag: tag, |
845 | + UpgradedToVersion: version.Current.Number, |
846 | + Password: password, |
847 | + Nonce: cfg.MachineNonce, |
848 | + StateAddresses: cfg.stateHostAddrs(), |
849 | + APIAddresses: cfg.apiHostAddrs(), |
850 | + CACert: cfg.StateInfo.CACert, |
851 | + Values: cfg.AgentEnvironment, |
852 | } |
853 | if !cfg.StateServer { |
854 | return agent.NewAgentConfig(configParams) |
855 | |
856 | === modified file 'juju/testing/conn.go' |
857 | --- juju/testing/conn.go 2014-02-20 04:59:31 +0000 |
858 | +++ juju/testing/conn.go 2014-02-26 03:43:42 +0000 |
859 | @@ -300,13 +300,14 @@ |
860 | c.Assert(err, gc.IsNil) |
861 | config, err := agent.NewAgentConfig( |
862 | agent.AgentConfigParams{ |
863 | - DataDir: s.DataDir(), |
864 | - Tag: tag, |
865 | - Password: password, |
866 | - Nonce: "nonce", |
867 | - StateAddresses: s.StateInfo(c).Addrs, |
868 | - APIAddresses: s.APIInfo(c).Addrs, |
869 | - CACert: []byte(testing.CACert), |
870 | + DataDir: s.DataDir(), |
871 | + Tag: tag, |
872 | + UpgradedToVersion: version.Current.Number, |
873 | + Password: password, |
874 | + Nonce: "nonce", |
875 | + StateAddresses: s.StateInfo(c).Addrs, |
876 | + APIAddresses: s.APIInfo(c).Addrs, |
877 | + CACert: []byte(testing.CACert), |
878 | }) |
879 | c.Assert(err, gc.IsNil) |
880 | return config |
881 | |
882 | === modified file 'worker/deployer/simple.go' |
883 | --- worker/deployer/simple.go 2014-02-12 07:24:47 +0000 |
884 | +++ worker/deployer/simple.go 2014-02-26 03:43:42 +0000 |
885 | @@ -102,10 +102,11 @@ |
886 | namespace := ctx.agentConfig.Value(agent.Namespace) |
887 | conf, err := agent.NewAgentConfig( |
888 | agent.AgentConfigParams{ |
889 | - DataDir: dataDir, |
890 | - Tag: tag, |
891 | - Password: initialPassword, |
892 | - Nonce: "unused", |
893 | + DataDir: dataDir, |
894 | + Tag: tag, |
895 | + UpgradedToVersion: version.Current.Number, |
896 | + Password: initialPassword, |
897 | + Nonce: "unused", |
898 | // TODO: remove the state addresses here and test when api only. |
899 | StateAddresses: result.StateAddresses, |
900 | APIAddresses: result.APIAddresses, |
901 | |
902 | === modified file 'worker/provisioner/kvm-broker_test.go' |
903 | --- worker/provisioner/kvm-broker_test.go 2014-01-22 19:28:08 +0000 |
904 | +++ worker/provisioner/kvm-broker_test.go 2014-02-26 03:43:42 +0000 |
905 | @@ -66,12 +66,13 @@ |
906 | var err error |
907 | s.agentConfig, err = agent.NewAgentConfig( |
908 | agent.AgentConfigParams{ |
909 | - DataDir: "/not/used/here", |
910 | - Tag: "tag", |
911 | - Password: "dummy-secret", |
912 | - Nonce: "nonce", |
913 | - APIAddresses: []string{"10.0.0.1:1234"}, |
914 | - CACert: []byte(coretesting.CACert), |
915 | + DataDir: "/not/used/here", |
916 | + Tag: "tag", |
917 | + UpgradedToVersion: version.Current.Number, |
918 | + Password: "dummy-secret", |
919 | + Nonce: "nonce", |
920 | + APIAddresses: []string{"10.0.0.1:1234"}, |
921 | + CACert: []byte(coretesting.CACert), |
922 | }) |
923 | c.Assert(err, gc.IsNil) |
924 | s.broker, err = provisioner.NewKvmBroker(&fakeAPI{}, tools, s.agentConfig) |
925 | |
926 | === modified file 'worker/provisioner/lxc-broker_test.go' |
927 | --- worker/provisioner/lxc-broker_test.go 2014-02-14 11:33:57 +0000 |
928 | +++ worker/provisioner/lxc-broker_test.go 2014-02-26 03:43:42 +0000 |
929 | @@ -68,12 +68,13 @@ |
930 | var err error |
931 | s.agentConfig, err = agent.NewAgentConfig( |
932 | agent.AgentConfigParams{ |
933 | - DataDir: "/not/used/here", |
934 | - Tag: "tag", |
935 | - Password: "dummy-secret", |
936 | - Nonce: "nonce", |
937 | - APIAddresses: []string{"10.0.0.1:1234"}, |
938 | - CACert: []byte(coretesting.CACert), |
939 | + DataDir: "/not/used/here", |
940 | + Tag: "tag", |
941 | + UpgradedToVersion: version.Current.Number, |
942 | + Password: "dummy-secret", |
943 | + Nonce: "nonce", |
944 | + APIAddresses: []string{"10.0.0.1:1234"}, |
945 | + CACert: []byte(coretesting.CACert), |
946 | }) |
947 | c.Assert(err, gc.IsNil) |
948 | s.broker = provisioner.NewLxcBroker(&fakeAPI{}, tools, s.agentConfig) |
Reviewers: mp+207600_ code.launchpad. net,
Message:
Please take a look.
Description:
Record upgradedToVersion in agent conf
The agent config files now record the version for
which upgrade steps were run. If there is no entry
in a config file, is is assumed the version is 1.16.
So there is no need for any migration, nor are there
compatibility issues. When 1.18 is deployed, the upgrade
steps are run and the config file will have 1.18 written
to it, allowing 1.20 to know that it is upgrding from
1.18 etc.
https:/ /code.launchpad .net/~wallyworl d/juju- core/agent- conf-upgradedto /+merge/ 207600
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/66870044/
Affected files (+332, -203 lines): _test.go 1.12_whitebox_ test.go 1.16.go 1.16_whitebox_ test.go whitebox_ test.go agent_test. go bootstrap_ test.go cloudinit/ cloudinit. go conn.go deployer/ simple. go provisioner/ kvm-broker_ test.go provisioner/ lxc-broker_ test.go
A [revision details]
M agent/agent.go
M agent/agent_test.go
M agent/bootstrap
M agent/format-
M agent/format-
M agent/format-
M agent/format_
M cmd/jujud/
M cmd/jujud/
M environs/
M juju/testing/
M worker/
M worker/
M worker/