Merge lp:~ericsnowcurrently/fake-juju/juju-2.0-support into lp:~landscape/fake-juju/trunk-old
- juju-2.0-support
- Merge into trunk-old
Status: | Superseded |
---|---|
Proposed branch: | lp:~ericsnowcurrently/fake-juju/juju-2.0-support |
Merge into: | lp:~landscape/fake-juju/trunk-old |
Diff against target: |
2443 lines (+826/-902) 11 files modified
1.24.7/fake-juju.go (+0/-514) 1.25.6/fake-juju.go (+275/-109) 2.0.0/fake-juju.go (+287/-156) Makefile (+3/-3) patches/juju-core_1.24.7.patch (+0/-47) patches/juju-core_2.0.0.patch (+6/-6) python/Makefile (+1/-1) python/fakejuju/fakejuju.py (+44/-23) python/fakejuju/testing.py (+5/-19) python/fakejuju/tests/test_fakejuju.py (+204/-23) tests/test_fake.py (+1/-1) |
To merge this branch: | bzr merge lp:~ericsnowcurrently/fake-juju/juju-2.0-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
🤖 Landscape Builder | test results | Approve | |
Landscape | Pending | ||
Landscape | Pending | ||
Review via email: mp+309284@code.launchpad.net |
This proposal has been superseded by a proposal from 2016-10-25.
Commit message
Description of the change
Update to juju 2.0.0.
Also drop support for 1.24.x.
Testing instructions:
Run make test
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
- 64. By Eric Snow
-
Add some comments about bootstrap.
- 65. By Eric Snow
-
Factor out waitForBootstra
pCompletion( ). - 66. By Eric Snow
-
Rename the command handler functions.
- 67. By Eric Snow
-
Factor out destroyControll
er(). - 68. By Eric Snow
-
Destroy the controller if bootstrap fails.
- 69. By Eric Snow
-
Factor out updateBootstrap
Result( ). - 70. By Eric Snow
-
Call copyConfig() before updating the bootstrap result.
- 71. By Eric Snow
-
Handle bootstrap error cleanup centrally.
- 72. By Eric Snow
-
Capture error output from the daemon.
- 73. By Eric Snow
-
Minor touch-ups to SetUpTest().
- 74. By Eric Snow
-
Use constants for the environment variable names.
- 75. By Eric Snow
-
Add a log file for jujud logs.
- 76. By Eric Snow
-
Factor out reportInfo().
- 77. By Eric Snow
-
Ensure that the daemon runs with FAKE_JUJU_DATA_DIR set.
- 78. By Eric Snow
-
Parse all the supported "juju bootstrap" args.
- 79. By Eric Snow
-
Always ensure that the Juju cfg dir exists.
- 80. By Eric Snow
-
Support a -v bootstrap arg.
- 81. By Eric Snow
-
Set up logging right *after* calling JujuConnSuite.
SetUpTest( ). - 82. By Eric Snow
-
Fix bootstrap arg order in a test.
- 83. By Eric Snow
-
Merge from trunk.
- 84. By Eric Snow
-
Copy some of the 2.0.0 tweaks over to 1.25.6.
- 85. By Eric Snow
-
Default to Juju 2.x in tests.
- 86. By Eric Snow
-
Pull a little more code from 2.0.0.
Unmerged revisions
Preview Diff
1 | === removed directory '1.24.7' |
2 | === removed file '1.24.7/fake-juju.go' |
3 | --- 1.24.7/fake-juju.go 2016-06-10 17:08:28 +0000 |
4 | +++ 1.24.7/fake-juju.go 1970-01-01 00:00:00 +0000 |
5 | @@ -1,514 +0,0 @@ |
6 | -package main |
7 | - |
8 | -import ( |
9 | - "bufio" |
10 | - "encoding/json" |
11 | - "errors" |
12 | - "fmt" |
13 | - gc "gopkg.in/check.v1" |
14 | - "io" |
15 | - "io/ioutil" |
16 | - "log" |
17 | - "os" |
18 | - "os/exec" |
19 | - "path/filepath" |
20 | - "strings" |
21 | - "syscall" |
22 | - "testing" |
23 | - "time" |
24 | - |
25 | - "github.com/juju/juju/agent" |
26 | - "github.com/juju/juju/api" |
27 | - "github.com/juju/juju/environs" |
28 | - "github.com/juju/juju/environs/configstore" |
29 | - "github.com/juju/juju/instance" |
30 | - "github.com/juju/juju/juju/osenv" |
31 | - jujutesting "github.com/juju/juju/juju/testing" |
32 | - "github.com/juju/juju/network" |
33 | - _ "github.com/juju/juju/provider/maas" |
34 | - "github.com/juju/juju/state" |
35 | - coretesting "github.com/juju/juju/testing" |
36 | - "github.com/juju/juju/testing/factory" |
37 | - "github.com/juju/juju/version" |
38 | - "github.com/juju/names" |
39 | - corecharm "gopkg.in/juju/charm.v5/charmrepo" |
40 | - goyaml "gopkg.in/yaml.v1" |
41 | -) |
42 | - |
43 | -func main() { |
44 | - if len(os.Args) > 1 { |
45 | - code := 0 |
46 | - err := handleCommand(os.Args[1]) |
47 | - if err != nil { |
48 | - fmt.Println(err.Error()) |
49 | - code = 1 |
50 | - } |
51 | - os.Exit(code) |
52 | - } |
53 | - t := &testing.T{} |
54 | - coretesting.MgoTestPackage(t) |
55 | -} |
56 | - |
57 | -type processInfo struct { |
58 | - WorkDir string |
59 | - EndpointAddr string |
60 | - Uuid string |
61 | - CACert string |
62 | -} |
63 | - |
64 | -func handleCommand(command string) error { |
65 | - if command == "bootstrap" { |
66 | - return bootstrap() |
67 | - } |
68 | - if command == "api-endpoints" { |
69 | - return apiEndpoints() |
70 | - } |
71 | - if command == "api-info" { |
72 | - return apiInfo() |
73 | - } |
74 | - if command == "destroy-environment" { |
75 | - return destroyEnvironment() |
76 | - } |
77 | - return errors.New("command not found") |
78 | -} |
79 | - |
80 | -func bootstrap() error { |
81 | - envName, password, err := environmentNameAndPassword() |
82 | - if err != nil { |
83 | - return err |
84 | - } |
85 | - command := exec.Command(os.Args[0]) |
86 | - command.Env = os.Environ() |
87 | - command.Env = append(command.Env, "ADMIN_PASSWORD="+password) |
88 | - stdout, err := command.StdoutPipe() |
89 | - if err != nil { |
90 | - return err |
91 | - } |
92 | - command.Start() |
93 | - apiInfo, err := parseApiInfo(envName, stdout) |
94 | - if err != nil { |
95 | - return err |
96 | - } |
97 | - dialOpts := api.DialOpts{ |
98 | - DialAddressInterval: 50 * time.Millisecond, |
99 | - Timeout: 5 * time.Second, |
100 | - RetryDelay: 2 * time.Second, |
101 | - } |
102 | - state, err := api.Open(apiInfo, dialOpts) |
103 | - if err != nil { |
104 | - return err |
105 | - } |
106 | - client := state.Client() |
107 | - watcher, err := client.WatchAll() |
108 | - if err != nil { |
109 | - return err |
110 | - } |
111 | - deltas, err := watcher.Next() |
112 | - if err != nil { |
113 | - return err |
114 | - } |
115 | - for _, delta := range deltas { |
116 | - entityId := delta.Entity.EntityId() |
117 | - if entityId.Kind == "machine" { |
118 | - machineId, _ := entityId.Id.(string) |
119 | - if machineId == "0" { |
120 | - return nil |
121 | - } |
122 | - } |
123 | - } |
124 | - return errors.New("invalid delta") |
125 | -} |
126 | - |
127 | -func apiEndpoints() error { |
128 | - info, err := readProcessInfo() |
129 | - if err != nil { |
130 | - return err |
131 | - } |
132 | - fmt.Println(info.EndpointAddr) |
133 | - return nil |
134 | -} |
135 | - |
136 | -func apiInfo() error { |
137 | - info, err := readProcessInfo() |
138 | - if err != nil { |
139 | - return err |
140 | - } |
141 | - fmt.Printf("{\"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", info.Uuid, info.EndpointAddr) |
142 | - return nil |
143 | -} |
144 | - |
145 | -func destroyEnvironment() error { |
146 | - info, err := readProcessInfo() |
147 | - if err != nil { |
148 | - return err |
149 | - } |
150 | - fifoPath := filepath.Join(info.WorkDir, "fifo") |
151 | - fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600) |
152 | - if err != nil { |
153 | - return err |
154 | - } |
155 | - defer fd.Close() |
156 | - _, err = fd.WriteString("destroy\n") |
157 | - if err != nil { |
158 | - return err |
159 | - } |
160 | - return nil |
161 | -} |
162 | - |
163 | -func environmentNameAndPassword() (string, string, error) { |
164 | - jujuHome := os.Getenv("JUJU_HOME") |
165 | - osenv.SetJujuHome(jujuHome) |
166 | - environs, err := environs.ReadEnvirons( |
167 | - filepath.Join(jujuHome, "environments.yaml")) |
168 | - if err != nil { |
169 | - return "", "", err |
170 | - } |
171 | - envName := environs.Names()[0] |
172 | - config, err := environs.Config(envName) |
173 | - if err != nil { |
174 | - return "", "", err |
175 | - } |
176 | - return envName, config.AdminSecret(), nil |
177 | -} |
178 | - |
179 | -func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) { |
180 | - buffer := bufio.NewReader(stdout) |
181 | - line, _, err := buffer.ReadLine() |
182 | - if err != nil { |
183 | - return nil, err |
184 | - } |
185 | - uuid := string(line) |
186 | - environTag := names.NewEnvironTag(uuid) |
187 | - line, _, err = buffer.ReadLine() |
188 | - if err != nil { |
189 | - return nil, err |
190 | - } |
191 | - workDir := string(line) |
192 | - store, err := configstore.NewDisk(workDir) |
193 | - if err != nil { |
194 | - return nil, err |
195 | - } |
196 | - info, err := store.ReadInfo("dummyenv") |
197 | - if err != nil { |
198 | - return nil, err |
199 | - } |
200 | - credentials := info.APICredentials() |
201 | - endpoint := info.APIEndpoint() |
202 | - addresses := endpoint.Addresses |
203 | - apiInfo := &api.Info{ |
204 | - Addrs: addresses, |
205 | - Tag: names.NewLocalUserTag(credentials.User), |
206 | - Password: credentials.Password, |
207 | - CACert: endpoint.CACert, |
208 | - EnvironTag: environTag, |
209 | - } |
210 | - err = writeProcessInfo(envName, &processInfo{ |
211 | - WorkDir: workDir, |
212 | - EndpointAddr: addresses[0], |
213 | - Uuid: uuid, |
214 | - CACert: endpoint.CACert, |
215 | - }) |
216 | - if err != nil { |
217 | - return nil, err |
218 | - } |
219 | - return apiInfo, nil |
220 | -} |
221 | - |
222 | -func readProcessInfo() (*processInfo, error) { |
223 | - infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju") |
224 | - data, err := ioutil.ReadFile(infoPath) |
225 | - if err != nil { |
226 | - return nil, err |
227 | - } |
228 | - info := &processInfo{} |
229 | - err = goyaml.Unmarshal(data, info) |
230 | - if err != nil { |
231 | - return nil, err |
232 | - } |
233 | - return info, nil |
234 | -} |
235 | - |
236 | -func writeProcessInfo(envName string, info *processInfo) error { |
237 | - jujuHome := os.Getenv("JUJU_HOME") |
238 | - infoPath := filepath.Join(jujuHome, "fakejuju") |
239 | - logPath := filepath.Join(jujuHome, "fake-juju.log") |
240 | - caCertPath := filepath.Join(jujuHome, "cert.ca") |
241 | - envPath := filepath.Join(jujuHome, "environments") |
242 | - os.Mkdir(envPath, 0755) |
243 | - jEnvPath := filepath.Join(envPath, envName+".jenv") |
244 | - data, _ := goyaml.Marshal(info) |
245 | - err := os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath) |
246 | - if err != nil { |
247 | - return err |
248 | - } |
249 | - err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath) |
250 | - if err != nil { |
251 | - return err |
252 | - } |
253 | - err = ioutil.WriteFile(infoPath, data, 0644) |
254 | - if err != nil { |
255 | - return err |
256 | - } |
257 | - return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644) |
258 | -} |
259 | - |
260 | -type FakeJujuSuite struct { |
261 | - jujutesting.JujuConnSuite |
262 | - |
263 | - instanceCount int |
264 | - machineStarted map[string]bool |
265 | - fifoPath string |
266 | - logFile *os.File |
267 | -} |
268 | - |
269 | -var _ = gc.Suite(&FakeJujuSuite{}) |
270 | - |
271 | -func (s *FakeJujuSuite) SetUpTest(c *gc.C) { |
272 | - var CommandOutput = (*exec.Cmd).CombinedOutput |
273 | - s.JujuConnSuite.SetUpTest(c) |
274 | - |
275 | - ports := s.APIState.APIHostPorts() |
276 | - ports[0][0].NetworkName = "dummy-provider-network" |
277 | - err := s.State.SetAPIHostPorts(ports) |
278 | - c.Assert(err, gc.IsNil) |
279 | - |
280 | - s.machineStarted = make(map[string]bool) |
281 | - s.PatchValue(&corecharm.CacheDir, c.MkDir()) |
282 | - password := "dummy-password" |
283 | - if os.Getenv("ADMIN_PASSWORD") != "" { |
284 | - password = os.Getenv("ADMIN_PASSWORD") |
285 | - } |
286 | - _, err = s.State.AddUser("admin", "Admin", password, "dummy-admin") |
287 | - c.Assert(err, gc.IsNil) |
288 | - _, err = s.State.AddEnvironmentUser( |
289 | - names.NewLocalUserTag("admin"), names.NewLocalUserTag("dummy-admin"), "Admin") |
290 | - c.Assert(err, gc.IsNil) |
291 | - |
292 | - // Create a machine to manage the environment. |
293 | - stateServer := s.Factory.MakeMachine(c, &factory.MachineParams{ |
294 | - InstanceId: s.newInstanceId(), |
295 | - Nonce: agent.BootstrapNonce, |
296 | - Jobs: []state.MachineJob{state.JobManageEnviron, state.JobHostUnits}, |
297 | - Series: "trusty", |
298 | - }) |
299 | - c.Assert(stateServer.SetAgentVersion(version.Current), gc.IsNil) |
300 | - address := network.NewScopedAddress("127.0.0.1", network.ScopeCloudLocal) |
301 | - c.Assert(stateServer.SetProviderAddresses(address), gc.IsNil) |
302 | - c.Assert(stateServer.SetStatus(state.StatusStarted, "", nil), gc.IsNil) |
303 | - _, err = stateServer.SetAgentPresence() |
304 | - c.Assert(err, gc.IsNil) |
305 | - s.State.StartSync() |
306 | - err = stateServer.WaitAgentPresence(coretesting.LongWait) |
307 | - c.Assert(err, gc.IsNil) |
308 | - |
309 | - apiInfo := s.APIInfo(c) |
310 | - //fmt.Println(apiInfo.Addrs[0]) |
311 | - jujuHome := osenv.JujuHome() |
312 | - fmt.Println(apiInfo.EnvironTag.Id()) |
313 | - fmt.Println(jujuHome) |
314 | - |
315 | - binPath := filepath.Join(jujuHome, "bin") |
316 | - os.Mkdir(binPath, 0755) |
317 | - fakeSSHData := []byte("#!/bin/sh\nsleep 1\n") |
318 | - fakeSSHPath := filepath.Join(binPath, "ssh") |
319 | - err = ioutil.WriteFile(fakeSSHPath, fakeSSHData, 0755) |
320 | - c.Assert(err, gc.IsNil) |
321 | - os.Setenv("PATH", binPath+":"+os.Getenv("PATH")) |
322 | - |
323 | - s.fifoPath = filepath.Join(jujuHome, "fifo") |
324 | - syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0) |
325 | - |
326 | - // Logging |
327 | - logPath := filepath.Join(jujuHome, "fake-juju.log") |
328 | - s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
329 | - c.Assert(err, gc.IsNil) |
330 | - |
331 | - log.SetOutput(s.logFile) |
332 | - dpkgCmd := exec.Command( |
333 | - "dpkg-query", "--showformat='${Version}'", "--show", "fake-juju") |
334 | - out, err := CommandOutput(dpkgCmd) |
335 | - fakeJujuDebVersion := strings.Trim(string(out), "'") |
336 | - log.Printf("Started fake-juju-%s for %s\nJUJU_HOME=%s", fakeJujuDebVersion, version.Current, jujuHome) |
337 | - |
338 | -} |
339 | - |
340 | -func (s *FakeJujuSuite) TearDownTest(c *gc.C) { |
341 | - s.JujuConnSuite.TearDownTest(c) |
342 | - s.logFile.Close() |
343 | -} |
344 | - |
345 | -func (s *FakeJujuSuite) TestStart(c *gc.C) { |
346 | - watcher := s.State.Watch() |
347 | - go func() { |
348 | - fd, err := os.Open(s.fifoPath) |
349 | - c.Assert(err, gc.IsNil) |
350 | - scanner := bufio.NewScanner(fd) |
351 | - scanner.Scan() |
352 | - watcher.Stop() |
353 | - }() |
354 | - for { |
355 | - deltas, err := watcher.Next() |
356 | - log.Println("Got deltas") |
357 | - if err != nil { |
358 | - if err.Error() == "watcher was stopped" { |
359 | - log.Println("Watcher stopped") |
360 | - break |
361 | - } |
362 | - log.Println("Unexpected error", err.Error()) |
363 | - } |
364 | - c.Assert(err, gc.IsNil) |
365 | - for _, d := range deltas { |
366 | - |
367 | - entity, err := json.MarshalIndent(d.Entity, "", " ") |
368 | - c.Assert(err, gc.IsNil) |
369 | - verb := "change" |
370 | - if d.Removed { |
371 | - verb = "remove" |
372 | - } |
373 | - log.Println("Processing delta", verb, d.Entity.EntityId().Kind, string(entity[:])) |
374 | - |
375 | - entityId := d.Entity.EntityId() |
376 | - if entityId.Kind == "machine" { |
377 | - machineId, ok := entityId.Id.(string) |
378 | - c.Assert(ok, gc.Equals, true) |
379 | - c.Assert(s.handleAddMachine(machineId), gc.IsNil) |
380 | - } |
381 | - if entityId.Kind == "unit" { |
382 | - unitId, ok := entityId.Id.(string) |
383 | - c.Assert(ok, gc.Equals, true) |
384 | - c.Assert(s.handleAddUnit(unitId), gc.IsNil) |
385 | - } |
386 | - log.Println("Done processing delta") |
387 | - } |
388 | - } |
389 | -} |
390 | - |
391 | -func (s *FakeJujuSuite) handleAddMachine(id string) error { |
392 | - machine, err := s.State.Machine(id) |
393 | - if err != nil { |
394 | - return err |
395 | - } |
396 | - if instanceId, _ := machine.InstanceId(); instanceId == "" { |
397 | - err = machine.SetProvisioned(s.newInstanceId(), agent.BootstrapNonce, nil) |
398 | - if err != nil { |
399 | - log.Println("Got error with SetProvisioned", err) |
400 | - return err |
401 | - } |
402 | - address := network.NewScopedAddress("127.0.0.1", network.ScopeCloudLocal) |
403 | - err = machine.SetProviderAddresses(address) |
404 | - if err != nil { |
405 | - log.Println("Got error with SetProviderAddresses", err) |
406 | - return err |
407 | - } |
408 | - } |
409 | - status, _ := machine.Status() |
410 | - if status.Status == state.StatusPending { |
411 | - if err = s.startMachine(machine); err != nil { |
412 | - log.Println("Got error with startMachine:", err) |
413 | - return err |
414 | - } |
415 | - } else if status.Status == state.StatusStarted { |
416 | - if _, ok := s.machineStarted[id]; !ok { |
417 | - s.machineStarted[id] = true |
418 | - if err = s.startUnits(machine); err != nil { |
419 | - log.Println("Got error with startUnits", err) |
420 | - return err |
421 | - } |
422 | - } |
423 | - } |
424 | - return nil |
425 | -} |
426 | - |
427 | -func (s *FakeJujuSuite) handleAddUnit(id string) error { |
428 | - unit, err := s.State.Unit(id) |
429 | - if err != nil { |
430 | - log.Println("Got error with get unit", err) |
431 | - return err |
432 | - } |
433 | - machineId, err := unit.AssignedMachineId() |
434 | - if err != nil { |
435 | - return nil |
436 | - } |
437 | - log.Println("Got machineId", machineId) |
438 | - machine, err := s.State.Machine(machineId) |
439 | - if err != nil { |
440 | - log.Println("Got error with unit AssignedMachineId", err) |
441 | - return err |
442 | - } |
443 | - machineStatus, _ := machine.Status() |
444 | - if machineStatus.Status != state.StatusStarted { |
445 | - return nil |
446 | - } |
447 | - status, _ := unit.Status() |
448 | - if status.Status != state.StatusActive { |
449 | - if err = s.startUnit(unit); err != nil { |
450 | - return err |
451 | - } |
452 | - } |
453 | - return nil |
454 | -} |
455 | - |
456 | -func (s *FakeJujuSuite) startMachine(machine *state.Machine) error { |
457 | - time.Sleep(500 * time.Millisecond) |
458 | - err := machine.SetStatus(state.StatusStarted, "", nil) |
459 | - if err != nil { |
460 | - return err |
461 | - } |
462 | - err = machine.SetAgentVersion(version.Current) |
463 | - if err != nil { |
464 | - return err |
465 | - } |
466 | - _, err = machine.SetAgentPresence() |
467 | - if err != nil { |
468 | - return err |
469 | - } |
470 | - s.State.StartSync() |
471 | - err = machine.WaitAgentPresence(coretesting.LongWait) |
472 | - if err != nil { |
473 | - return err |
474 | - } |
475 | - return nil |
476 | -} |
477 | - |
478 | -func (s *FakeJujuSuite) startUnits(machine *state.Machine) error { |
479 | - units, err := machine.Units() |
480 | - if err != nil { |
481 | - return err |
482 | - } |
483 | - return nil |
484 | - for _, unit := range units { |
485 | - unitStatus, _ := unit.Status() |
486 | - if unitStatus.Status != state.StatusActive { |
487 | - if err = s.startUnit(unit); err != nil { |
488 | - return err |
489 | - } |
490 | - } |
491 | - } |
492 | - return nil |
493 | -} |
494 | - |
495 | -func (s *FakeJujuSuite) startUnit(unit *state.Unit) error { |
496 | - err := unit.SetStatus(state.StatusActive, "", nil) |
497 | - if err != nil { |
498 | - return err |
499 | - } |
500 | - _, err = unit.SetAgentPresence() |
501 | - if err != nil { |
502 | - return err |
503 | - } |
504 | - s.State.StartSync() |
505 | - err = unit.WaitAgentPresence(coretesting.LongWait) |
506 | - if err != nil { |
507 | - return err |
508 | - } |
509 | - err = unit.SetAgentStatus(state.StatusIdle, "", nil) |
510 | - if err != nil { |
511 | - return err |
512 | - } |
513 | - return nil |
514 | -} |
515 | - |
516 | -func (s *FakeJujuSuite) newInstanceId() instance.Id { |
517 | - s.instanceCount += 1 |
518 | - return instance.Id(fmt.Sprintf("id-%d", s.instanceCount)) |
519 | -} |
520 | |
521 | === modified file '1.25.6/fake-juju.go' |
522 | --- 1.25.6/fake-juju.go 2016-06-10 17:07:27 +0000 |
523 | +++ 1.25.6/fake-juju.go 2016-10-25 17:25:06 +0000 |
524 | @@ -37,52 +37,51 @@ |
525 | ) |
526 | |
527 | func main() { |
528 | + code := 0 |
529 | if len(os.Args) > 1 { |
530 | - code := 0 |
531 | err := handleCommand(os.Args[1]) |
532 | if err != nil { |
533 | fmt.Println(err.Error()) |
534 | code = 1 |
535 | } |
536 | - os.Exit(code) |
537 | + } else { |
538 | + // This kicks off the daemon. See FakeJujuSuite below. |
539 | + t := &testing.T{} |
540 | + coretesting.MgoTestPackage(t) |
541 | } |
542 | - t := &testing.T{} |
543 | - coretesting.MgoTestPackage(t) |
544 | -} |
545 | - |
546 | -type processInfo struct { |
547 | - Username string |
548 | - WorkDir string |
549 | - EndpointAddr string |
550 | - Uuid string |
551 | - CACert string |
552 | + os.Exit(code) |
553 | } |
554 | |
555 | func handleCommand(command string) error { |
556 | + filenames := newFakeJujuFilenames("", "", "") |
557 | if command == "bootstrap" { |
558 | - return bootstrap() |
559 | + return bootstrap(filenames) |
560 | } |
561 | if command == "api-endpoints" { |
562 | - return apiEndpoints() |
563 | + return apiEndpoints(filenames) |
564 | } |
565 | if command == "api-info" { |
566 | - return apiInfo() |
567 | + return apiInfo(filenames) |
568 | } |
569 | if command == "destroy-environment" { |
570 | - return destroyEnvironment() |
571 | + return destroyEnvironment(filenames) |
572 | } |
573 | return errors.New("command not found") |
574 | } |
575 | |
576 | -func bootstrap() error { |
577 | +func bootstrap(filenames fakejujuFilenames) error { |
578 | + if err := filenames.ensureDirsExist(); err != nil { |
579 | + return err |
580 | + } |
581 | envName, config, err := environmentNameAndConfig() |
582 | if err != nil { |
583 | return err |
584 | } |
585 | + password := config.AdminSecret() |
586 | + |
587 | command := exec.Command(os.Args[0]) |
588 | command.Env = os.Environ() |
589 | - command.Env = append( |
590 | - command.Env, "ADMIN_PASSWORD="+config.AdminSecret()) |
591 | + command.Env = append(command.Env, "ADMIN_PASSWORD="+password) |
592 | defaultSeries, _ := config.DefaultSeries() |
593 | command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries) |
594 | stdout, err := command.StdoutPipe() |
595 | @@ -90,10 +89,23 @@ |
596 | return err |
597 | } |
598 | command.Start() |
599 | - apiInfo, err := parseApiInfo(envName, stdout) |
600 | + |
601 | + result, err := parseApiInfo(stdout) |
602 | if err != nil { |
603 | return err |
604 | } |
605 | + // Get the API info before changing it. The new values might |
606 | + // not work yet. |
607 | + apiInfo := result.apiInfo() |
608 | + // We actually want to report the API user we added in SetUpTest(). |
609 | + result.username = "admin" |
610 | + if password != "" { |
611 | + result.password = password |
612 | + } |
613 | + if err := result.apply(filenames, envName); err != nil { |
614 | + return err |
615 | + } |
616 | + |
617 | dialOpts := api.DialOpts{ |
618 | DialAddressInterval: 50 * time.Millisecond, |
619 | Timeout: 5 * time.Second, |
620 | @@ -123,8 +135,8 @@ |
621 | return errors.New("invalid delta") |
622 | } |
623 | |
624 | -func apiEndpoints() error { |
625 | - info, err := readProcessInfo() |
626 | +func apiEndpoints(filenames fakejujuFilenames) error { |
627 | + info, err := readProcessInfo(filenames) |
628 | if err != nil { |
629 | return err |
630 | } |
631 | @@ -132,23 +144,22 @@ |
632 | return nil |
633 | } |
634 | |
635 | -func apiInfo() error { |
636 | - info, err := readProcessInfo() |
637 | +func apiInfo(filenames fakejujuFilenames) error { |
638 | + info, err := readProcessInfo(filenames) |
639 | if err != nil { |
640 | return err |
641 | } |
642 | - username := strings.Replace(string(info.Username), "dummy-", "", 1) |
643 | - fmt.Printf("{\"user\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", username, info.Uuid, info.EndpointAddr) |
644 | + fmt.Printf("{\"user\": \"%s\", \"password\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", info.Username, info.Password, info.Uuid, info.EndpointAddr) |
645 | return nil |
646 | } |
647 | |
648 | -func destroyEnvironment() error { |
649 | - info, err := readProcessInfo() |
650 | +func destroyEnvironment(filenames fakejujuFilenames) error { |
651 | + info, err := readProcessInfo(filenames) |
652 | if err != nil { |
653 | return err |
654 | } |
655 | - fifoPath := filepath.Join(info.WorkDir, "fifo") |
656 | - fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600) |
657 | + filenames = newFakeJujuFilenames("", "", info.WorkDir) |
658 | + fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600) |
659 | if err != nil { |
660 | return err |
661 | } |
662 | @@ -176,93 +187,235 @@ |
663 | return envName, config, nil |
664 | } |
665 | |
666 | -func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) { |
667 | +// processInfo holds all the information that fake-juju uses internally. |
668 | +type processInfo struct { |
669 | + Username string |
670 | + Password string |
671 | + WorkDir string |
672 | + EndpointAddr string |
673 | + Uuid string |
674 | + CACert []byte |
675 | +} |
676 | + |
677 | +func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) { |
678 | + infoPath := filenames.infoFile() |
679 | + data, err := ioutil.ReadFile(infoPath) |
680 | + if err != nil { |
681 | + return nil, err |
682 | + } |
683 | + info := &processInfo{} |
684 | + err = goyaml.Unmarshal(data, info) |
685 | + if err != nil { |
686 | + return nil, err |
687 | + } |
688 | + return info, nil |
689 | +} |
690 | + |
691 | +func (info processInfo) write(infoPath string) error { |
692 | + data, _ := goyaml.Marshal(&info) |
693 | + if err := ioutil.WriteFile(infoPath, data, 0644); err != nil { |
694 | + return err |
695 | + } |
696 | + return nil |
697 | +} |
698 | + |
699 | +// fakejujuFilenames encapsulates the paths to all the directories and |
700 | +// files that are relevant to fake-juju. |
701 | +type fakejujuFilenames struct { |
702 | + datadir string |
703 | + logsdir string |
704 | +} |
705 | + |
706 | +func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames { |
707 | + if datadir == "" { |
708 | + datadir = os.Getenv("FAKE_JUJU_DATA_DIR") |
709 | + if datadir == "" { |
710 | + if jujucfgdir == "" { |
711 | + jujucfgdir = os.Getenv("JUJU_HOME") |
712 | + } |
713 | + datadir = jujucfgdir |
714 | + } |
715 | + } |
716 | + if logsdir == "" { |
717 | + logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR") |
718 | + if logsdir == "" { |
719 | + logsdir = datadir |
720 | + } |
721 | + } |
722 | + return fakejujuFilenames{datadir, logsdir} |
723 | +} |
724 | + |
725 | +func (fj fakejujuFilenames) ensureDirsExist() error { |
726 | + if err := os.MkdirAll(fj.datadir, 0755); err != nil { |
727 | + return err |
728 | + } |
729 | + if err := os.MkdirAll(fj.logsdir, 0755); err != nil { |
730 | + return err |
731 | + } |
732 | + return nil |
733 | +} |
734 | + |
735 | +// infoFile() returns the path to the file that fake-juju uses as |
736 | +// its persistent storage for internal data. |
737 | +func (fj fakejujuFilenames) infoFile() string { |
738 | + return filepath.Join(fj.datadir, "fakejuju") |
739 | +} |
740 | + |
741 | +// logsFile() returns the path to the file where fake-juju writes |
742 | +// its logs. Note that the normal Juju logs are not written here. |
743 | +func (fj fakejujuFilenames) logsFile() string { |
744 | + return filepath.Join(fj.logsdir, "fake-juju.log") |
745 | +} |
746 | + |
747 | +// fifoFile() returns the path to the FIFO file used by fake-juju. |
748 | +// The FIFO is used by the fake-juju subcommands to interact with |
749 | +// the daemon. |
750 | +func (fj fakejujuFilenames) fifoFile() string { |
751 | + return filepath.Join(fj.datadir, "fifo") |
752 | +} |
753 | + |
754 | +// caCertFile() returns the path to the file holding the CA certificate |
755 | +// used by the Juju API server. fake-juju writes the cert there as a |
756 | +// convenience for users. It is not actually used for anything. |
757 | +func (fj fakejujuFilenames) caCertFile() string { |
758 | + return filepath.Join(fj.datadir, "cert.ca") |
759 | +} |
760 | + |
761 | +// bootstrapResult encapsulates all significant information that came |
762 | +// from bootstrapping an environment. |
763 | +type bootstrapResult struct { |
764 | + dummyEnvName string |
765 | + cfgdir string |
766 | + uuid string |
767 | + username string |
768 | + password string |
769 | + addresses []string |
770 | + caCert []byte |
771 | +} |
772 | + |
773 | +// apiInfo() composes the Juju API info corresponding to the result. |
774 | +func (br bootstrapResult) apiInfo() *api.Info { |
775 | + return &api.Info{ |
776 | + Addrs: br.addresses, |
777 | + Tag: names.NewLocalUserTag(br.username), |
778 | + Password: br.password, |
779 | + CACert: string(br.caCert), |
780 | + EnvironTag: names.NewEnvironTag(br.uuid), |
781 | + } |
782 | +} |
783 | + |
784 | +// fakeJujuInfo() composes, from the result, the set of information |
785 | +// that fake-juju should use internally. |
786 | +func (br bootstrapResult) fakeJujuInfo() *processInfo { |
787 | + return &processInfo{ |
788 | + Username: br.username, |
789 | + Password: br.password, |
790 | + WorkDir: br.cfgdir, |
791 | + EndpointAddr: br.addresses[0], |
792 | + Uuid: br.uuid, |
793 | + CACert: br.caCert, |
794 | + } |
795 | +} |
796 | + |
797 | +// logsSymlinkFilenames() determines the source and target paths for |
798 | +// a symlink to the fake-juju logs file. Such a symlink is relevant |
799 | +// because the fake-juju daemon may not know where the log file is |
800 | +// meant to go. It defaults to putting the log file in the default Juju |
801 | +// config dir. In that case, a symlink should be created from there to |
802 | +// the user-defined Juju config dir ($JUJU_HOME). |
803 | +func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) { |
804 | + if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" || os.Getenv("FAKE_JUJU_DATA_DIR") != "" { |
805 | + return "", "" |
806 | + } |
807 | + |
808 | + filenames := newFakeJujuFilenames("", "", br.cfgdir) |
809 | + source = filenames.logsFile() |
810 | + target = targetLogsFile |
811 | + return source, target |
812 | +} |
813 | + |
814 | +// jenvSymlinkFilenames() determines the source and target paths for |
815 | +// a symlink to the .jenv file for the identified environment. |
816 | +func (br bootstrapResult) jenvSymlinkFilenames(jujuHome, envName string) (source, target string) { |
817 | + if jujuHome == "" || envName == "" { |
818 | + return "", "" |
819 | + } |
820 | + |
821 | + source = filepath.Join(br.cfgdir, "environments", br.dummyEnvName+".jenv") |
822 | + target = filepath.Join(jujuHome, "environments", envName+".jenv") |
823 | + return source, target |
824 | +} |
825 | + |
826 | +// apply() writes out the information from the bootstrap result to the |
827 | +// various files identified by the provided filenames. |
828 | +func (br bootstrapResult) apply(filenames fakejujuFilenames, envName string) error { |
829 | + if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil { |
830 | + return err |
831 | + } |
832 | + |
833 | + logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile()) |
834 | + if logsSource != "" && logsTarget != "" { |
835 | + if err := os.Symlink(logsSource, logsTarget); err != nil { |
836 | + return err |
837 | + } |
838 | + } |
839 | + |
840 | + jenvSource, jenvTarget := br.jenvSymlinkFilenames(os.Getenv("JUJU_HOME"), envName) |
841 | + if jenvSource != "" && jenvTarget != "" { |
842 | + if err := os.MkdirAll(filepath.Dir(jenvTarget), 0755); err != nil { |
843 | + return err |
844 | + } |
845 | + if err := os.Symlink(jenvSource, jenvTarget); err != nil { |
846 | + return err |
847 | + } |
848 | + } |
849 | + |
850 | + if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil { |
851 | + return err |
852 | + } |
853 | + |
854 | + return nil |
855 | +} |
856 | + |
857 | +// See github.com/juju/juju/blob/juju/testing/conn.go. |
858 | +const dummyEnvName = "dummyenv" |
859 | + |
860 | +func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) { |
861 | buffer := bufio.NewReader(stdout) |
862 | + |
863 | line, _, err := buffer.ReadLine() |
864 | if err != nil { |
865 | return nil, err |
866 | } |
867 | uuid := string(line) |
868 | - environTag := names.NewEnvironTag(uuid) |
869 | + |
870 | line, _, err = buffer.ReadLine() |
871 | if err != nil { |
872 | return nil, err |
873 | } |
874 | workDir := string(line) |
875 | + |
876 | store, err := configstore.NewDisk(workDir) |
877 | if err != nil { |
878 | return nil, err |
879 | } |
880 | - info, err := store.ReadInfo("dummyenv") |
881 | + info, err := store.ReadInfo(dummyEnvName) |
882 | if err != nil { |
883 | return nil, err |
884 | } |
885 | + |
886 | credentials := info.APICredentials() |
887 | endpoint := info.APIEndpoint() |
888 | - addresses := endpoint.Addresses |
889 | - apiInfo := &api.Info{ |
890 | - Addrs: addresses, |
891 | - Tag: names.NewLocalUserTag(credentials.User), |
892 | - Password: credentials.Password, |
893 | - CACert: endpoint.CACert, |
894 | - EnvironTag: environTag, |
895 | - } |
896 | - err = writeProcessInfo(envName, &processInfo{ |
897 | - Username: credentials.User, |
898 | - WorkDir: workDir, |
899 | - EndpointAddr: addresses[0], |
900 | - Uuid: uuid, |
901 | - CACert: endpoint.CACert, |
902 | - }) |
903 | - if err != nil { |
904 | - return nil, err |
905 | - } |
906 | - return apiInfo, nil |
907 | -} |
908 | - |
909 | -func readProcessInfo() (*processInfo, error) { |
910 | - infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju") |
911 | - data, err := ioutil.ReadFile(infoPath) |
912 | - if err != nil { |
913 | - return nil, err |
914 | - } |
915 | - info := &processInfo{} |
916 | - err = goyaml.Unmarshal(data, info) |
917 | - if err != nil { |
918 | - return nil, err |
919 | - } |
920 | - return info, nil |
921 | -} |
922 | - |
923 | -func writeProcessInfo(envName string, info *processInfo) error { |
924 | - var err error |
925 | - jujuHome := os.Getenv("JUJU_HOME") |
926 | - infoPath := filepath.Join(jujuHome, "fakejuju") |
927 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
928 | - if logsDir == "" { |
929 | - logsDir = jujuHome |
930 | - } |
931 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
932 | - caCertPath := filepath.Join(jujuHome, "cert.ca") |
933 | - envPath := filepath.Join(jujuHome, "environments") |
934 | - os.Mkdir(envPath, 0755) |
935 | - jEnvPath := filepath.Join(envPath, envName+".jenv") |
936 | - data, _ := goyaml.Marshal(info) |
937 | - if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" { |
938 | - err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath) |
939 | - if err != nil { |
940 | - return err |
941 | - } |
942 | - } |
943 | - err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath) |
944 | - if err != nil { |
945 | - return err |
946 | - } |
947 | - err = ioutil.WriteFile(infoPath, data, 0644) |
948 | - if err != nil { |
949 | - return err |
950 | - } |
951 | - return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644) |
952 | + result := &bootstrapResult{ |
953 | + dummyEnvName: dummyEnvName, |
954 | + cfgdir: workDir, |
955 | + uuid: uuid, |
956 | + username: credentials.User, |
957 | + password: credentials.Password, |
958 | + addresses: endpoint.Addresses, |
959 | + caCert: []byte(endpoint.CACert), |
960 | + } |
961 | + return result, nil |
962 | } |
963 | |
964 | // Read the failures info file pointed by the FAKE_JUJU_FAILURES environment |
965 | @@ -302,12 +455,16 @@ |
966 | return failuresInfo, nil |
967 | } |
968 | |
969 | +//=================================================================== |
970 | +// The fake-juju daemon (started by bootstrap) is found here. It is |
971 | +// implemented as a test suite. |
972 | + |
973 | type FakeJujuSuite struct { |
974 | jujutesting.JujuConnSuite |
975 | |
976 | instanceCount int |
977 | machineStarted map[string]bool |
978 | - fifoPath string |
979 | + filenames fakejujuFilenames |
980 | logFile *os.File |
981 | } |
982 | |
983 | @@ -359,7 +516,6 @@ |
984 | c.Assert(err, gc.IsNil) |
985 | |
986 | apiInfo := s.APIInfo(c) |
987 | - //fmt.Println(apiInfo.Addrs[0]) |
988 | jujuHome := osenv.JujuHome() |
989 | // IMPORTANT: don't remove this logging because it's used by the |
990 | // bootstrap command. |
991 | @@ -374,15 +530,11 @@ |
992 | c.Assert(err, gc.IsNil) |
993 | os.Setenv("PATH", binPath+":"+os.Getenv("PATH")) |
994 | |
995 | - s.fifoPath = filepath.Join(jujuHome, "fifo") |
996 | - syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0) |
997 | + s.filenames = newFakeJujuFilenames("", "", jujuHome) |
998 | + syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0) |
999 | |
1000 | // Logging |
1001 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
1002 | - if logsDir == "" { |
1003 | - logsDir = jujuHome |
1004 | - } |
1005 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
1006 | + logPath := s.filenames.logsFile() |
1007 | s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
1008 | c.Assert(err, gc.IsNil) |
1009 | |
1010 | @@ -402,16 +554,30 @@ |
1011 | } |
1012 | |
1013 | func (s *FakeJujuSuite) TestStart(c *gc.C) { |
1014 | + fifoPath := s.filenames.fifoFile() |
1015 | watcher := s.State.Watch() |
1016 | go func() { |
1017 | - log.Println("Open commands FIFO", s.fifoPath) |
1018 | - fd, err := os.Open(s.fifoPath) |
1019 | + log.Println("Open commands FIFO", fifoPath) |
1020 | + fd, err := os.Open(fifoPath) |
1021 | if err != nil { |
1022 | log.Println("Failed to open commands FIFO") |
1023 | } |
1024 | c.Assert(err, gc.IsNil) |
1025 | + defer func() { |
1026 | + if err := fd.Close(); err != nil { |
1027 | + c.Logf("failed closing FIFO file: %s", err) |
1028 | + } |
1029 | + // Mark the controller as destroyed by renaming some files. |
1030 | + if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil { |
1031 | + c.Logf("failed renaming FIFO file: %s", err) |
1032 | + } |
1033 | + infofile := s.filenames.infoFile() |
1034 | + if err := os.Rename(infofile, infofile+".destroyed"); err != nil { |
1035 | + c.Logf("failed renaming info file: %s", err) |
1036 | + } |
1037 | + }() |
1038 | scanner := bufio.NewScanner(fd) |
1039 | - log.Println("Listen for commands on FIFO", s.fifoPath) |
1040 | + log.Println("Listen for commands on FIFO", fifoPath) |
1041 | scanner.Scan() |
1042 | log.Println("Stopping fake-juju") |
1043 | watcher.Stop() |
1044 | |
1045 | === renamed directory '2.0-beta17' => '2.0.0' |
1046 | === modified file '2.0.0/fake-juju.go' |
1047 | --- 2.0-beta17/fake-juju.go 2016-09-15 19:05:50 +0000 |
1048 | +++ 2.0.0/fake-juju.go 2016-10-25 17:25:06 +0000 |
1049 | @@ -38,46 +38,45 @@ |
1050 | ) |
1051 | |
1052 | func main() { |
1053 | + code := 0 |
1054 | if len(os.Args) > 1 { |
1055 | - code := 0 |
1056 | err := handleCommand(os.Args[1]) |
1057 | if err != nil { |
1058 | fmt.Println(err.Error()) |
1059 | code = 1 |
1060 | } |
1061 | - os.Exit(code) |
1062 | + } else { |
1063 | + // This kicks off the daemon. See FakeJujuSuite below. |
1064 | + t := &testing.T{} |
1065 | + coretesting.MgoTestPackage(t) |
1066 | } |
1067 | - t := &testing.T{} |
1068 | - coretesting.MgoTestPackage(t) |
1069 | -} |
1070 | - |
1071 | -type processInfo struct { |
1072 | - WorkDir string |
1073 | - EndpointAddr string |
1074 | - Uuid string |
1075 | - CACert string |
1076 | + os.Exit(code) |
1077 | } |
1078 | |
1079 | func handleCommand(command string) error { |
1080 | + filenames := newFakeJujuFilenames("", "", "") |
1081 | if command == "bootstrap" { |
1082 | - return bootstrap() |
1083 | + return bootstrap(filenames) |
1084 | } |
1085 | if command == "show-controller" { |
1086 | - return apiInfo() |
1087 | + return apiInfo(filenames) |
1088 | } |
1089 | if command == "destroy-controller" { |
1090 | - return destroyEnvironment() |
1091 | + return destroyController(filenames) |
1092 | } |
1093 | return errors.New("command not found") |
1094 | } |
1095 | |
1096 | -func bootstrap() error { |
1097 | +func bootstrap(filenames fakejujuFilenames) error { |
1098 | argc := len(os.Args) |
1099 | if argc < 4 { |
1100 | return errors.New( |
1101 | "error: controller name and cloud name are required") |
1102 | } |
1103 | - envName := os.Args[argc-2] |
1104 | + if err := filenames.ensureDirsExist(); err != nil { |
1105 | + return err |
1106 | + } |
1107 | + controllerName := os.Args[argc-1] |
1108 | command := exec.Command(os.Args[0]) |
1109 | command.Env = os.Environ() |
1110 | command.Env = append( |
1111 | @@ -89,10 +88,16 @@ |
1112 | return err |
1113 | } |
1114 | command.Start() |
1115 | - apiInfo, err := parseApiInfo(envName, stdout) |
1116 | + |
1117 | + result, err := parseApiInfo(stdout) |
1118 | if err != nil { |
1119 | return err |
1120 | } |
1121 | + if err := result.apply(filenames, controllerName); err != nil { |
1122 | + return err |
1123 | + } |
1124 | + apiInfo := result.apiInfo() |
1125 | + |
1126 | dialOpts := api.DialOpts{ |
1127 | DialAddressInterval: 50 * time.Millisecond, |
1128 | Timeout: 5 * time.Second, |
1129 | @@ -122,8 +127,8 @@ |
1130 | return errors.New("invalid delta") |
1131 | } |
1132 | |
1133 | -func apiInfo() error { |
1134 | - info, err := readProcessInfo() |
1135 | +func apiInfo(filenames fakejujuFilenames) error { |
1136 | + info, err := readProcessInfo(filenames) |
1137 | if err != nil { |
1138 | return err |
1139 | } |
1140 | @@ -131,22 +136,24 @@ |
1141 | jujuHome := os.Getenv("JUJU_DATA") |
1142 | osenv.SetJujuXDGDataHome(jujuHome) |
1143 | cmd := controller.NewShowControllerCommand() |
1144 | - ctx, err := coretesting.RunCommandInDir( |
1145 | - nil, cmd, os.Args[2:], info.WorkDir) |
1146 | - if err != nil { |
1147 | + if err := coretesting.InitCommand(cmd, os.Args[2:]); err != nil { |
1148 | + return err |
1149 | + } |
1150 | + ctx := coretesting.ContextForDir(nil, info.WorkDir) |
1151 | + if err := cmd.Run(ctx); err != nil { |
1152 | return err |
1153 | } |
1154 | fmt.Print(ctx.Stdout) |
1155 | return nil |
1156 | } |
1157 | |
1158 | -func destroyEnvironment() error { |
1159 | - info, err := readProcessInfo() |
1160 | +func destroyController(filenames fakejujuFilenames) error { |
1161 | + info, err := readProcessInfo(filenames) |
1162 | if err != nil { |
1163 | return err |
1164 | } |
1165 | - fifoPath := filepath.Join(info.WorkDir, "fifo") |
1166 | - fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600) |
1167 | + filenames = newFakeJujuFilenames("", "", info.WorkDir) |
1168 | + fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600) |
1169 | if err != nil { |
1170 | return err |
1171 | } |
1172 | @@ -158,13 +165,214 @@ |
1173 | return nil |
1174 | } |
1175 | |
1176 | -func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) { |
1177 | +// processInfo holds all the information that fake-juju uses internally. |
1178 | +type processInfo struct { |
1179 | + WorkDir string |
1180 | + EndpointAddr string |
1181 | + Uuid string |
1182 | + CACert []byte |
1183 | +} |
1184 | + |
1185 | +func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) { |
1186 | + infoPath := filenames.infoFile() |
1187 | + data, err := ioutil.ReadFile(infoPath) |
1188 | + if err != nil { |
1189 | + return nil, err |
1190 | + } |
1191 | + info := &processInfo{} |
1192 | + err = goyaml.Unmarshal(data, info) |
1193 | + if err != nil { |
1194 | + return nil, err |
1195 | + } |
1196 | + return info, nil |
1197 | +} |
1198 | + |
1199 | +func (info processInfo) write(infoPath string) error { |
1200 | + data, _ := goyaml.Marshal(&info) |
1201 | + if err := ioutil.WriteFile(infoPath, data, 0644); err != nil { |
1202 | + return err |
1203 | + } |
1204 | + return nil |
1205 | +} |
1206 | + |
1207 | +// fakejujuFilenames encapsulates the paths to all the directories and |
1208 | +// files that are relevant to fake-juju. |
1209 | +type fakejujuFilenames struct { |
1210 | + datadir string |
1211 | + logsdir string |
1212 | +} |
1213 | + |
1214 | +func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames { |
1215 | + if datadir == "" { |
1216 | + datadir = os.Getenv("FAKE_JUJU_DATA_DIR") |
1217 | + if datadir == "" { |
1218 | + if jujucfgdir == "" { |
1219 | + jujucfgdir = os.Getenv("JUJU_DATA") |
1220 | + } |
1221 | + datadir = jujucfgdir |
1222 | + } |
1223 | + } |
1224 | + if logsdir == "" { |
1225 | + logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR") |
1226 | + if logsdir == "" { |
1227 | + logsdir = datadir |
1228 | + } |
1229 | + } |
1230 | + return fakejujuFilenames{datadir, logsdir} |
1231 | +} |
1232 | + |
1233 | +func (fj fakejujuFilenames) ensureDirsExist() error { |
1234 | + if err := os.MkdirAll(fj.datadir, 0755); err != nil { |
1235 | + return err |
1236 | + } |
1237 | + if err := os.MkdirAll(fj.logsdir, 0755); err != nil { |
1238 | + return err |
1239 | + } |
1240 | + return nil |
1241 | +} |
1242 | + |
1243 | +// infoFile() returns the path to the file that fake-juju uses as |
1244 | +// its persistent storage for internal data. |
1245 | +func (fj fakejujuFilenames) infoFile() string { |
1246 | + return filepath.Join(fj.datadir, "fakejuju") |
1247 | +} |
1248 | + |
1249 | +// logsFile() returns the path to the file where fake-juju writes |
1250 | +// its logs. Note that the normal Juju logs are not written here. |
1251 | +func (fj fakejujuFilenames) logsFile() string { |
1252 | + return filepath.Join(fj.logsdir, "fake-juju.log") |
1253 | +} |
1254 | + |
1255 | +// fifoFile() returns the path to the FIFO file used by fake-juju. |
1256 | +// The FIFO is used by the fake-juju subcommands to interact with |
1257 | +// the daemon. |
1258 | +func (fj fakejujuFilenames) fifoFile() string { |
1259 | + return filepath.Join(fj.datadir, "fifo") |
1260 | +} |
1261 | + |
1262 | +// caCertFile() returns the path to the file holding the CA certificate |
1263 | +// used by the Juju API server. fake-juju writes the cert there as a |
1264 | +// convenience for users. It is not actually used for anything. |
1265 | +func (fj fakejujuFilenames) caCertFile() string { |
1266 | + return filepath.Join(fj.datadir, "cert.ca") |
1267 | +} |
1268 | + |
1269 | +// bootstrapResult encapsulates all significant information that came |
1270 | +// from bootstrapping a controller. |
1271 | +type bootstrapResult struct { |
1272 | + dummyControllerName string |
1273 | + cfgdir string |
1274 | + uuid string |
1275 | + username string |
1276 | + password string |
1277 | + addresses []string |
1278 | + caCert []byte |
1279 | +} |
1280 | + |
1281 | +// apiInfo() composes the Juju API info corresponding to the result. |
1282 | +func (br bootstrapResult) apiInfo() *api.Info { |
1283 | + return &api.Info{ |
1284 | + Addrs: br.addresses, |
1285 | + Tag: names.NewUserTag(br.username), |
1286 | + Password: br.password, |
1287 | + CACert: string(br.caCert), |
1288 | + ModelTag: names.NewModelTag(br.uuid), |
1289 | + } |
1290 | +} |
1291 | + |
1292 | +// fakeJujuInfo() composes, from the result, the set of information |
1293 | +// that fake-juju should use internally. |
1294 | +func (br bootstrapResult) fakeJujuInfo() *processInfo { |
1295 | + return &processInfo{ |
1296 | + WorkDir: br.cfgdir, |
1297 | + EndpointAddr: br.addresses[0], |
1298 | + Uuid: br.uuid, |
1299 | + CACert: br.caCert, |
1300 | + } |
1301 | +} |
1302 | + |
1303 | +// logsSymlinkFilenames() determines the source and target paths for |
1304 | +// a symlink to the fake-juju logs file. Such a symlink is relevant |
1305 | +// because the fake-juju daemon may not know where the log file is |
1306 | +// meant to go. It defaults to putting the log file in the default Juju |
1307 | +// config dir. In that case, a symlink should be created from there to |
1308 | +// the user-defined Juju config dir ($JUJU_DATA). |
1309 | +func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) { |
1310 | + if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" { |
1311 | + return "", "" |
1312 | + } |
1313 | + |
1314 | + filenames := newFakeJujuFilenames("", "", br.cfgdir) |
1315 | + source = filenames.logsFile() |
1316 | + target = targetLogsFile |
1317 | + return source, target |
1318 | +} |
1319 | + |
1320 | +// apply() writes out the information from the bootstrap result to the |
1321 | +// various files identified by the provided filenames. |
1322 | +func (br bootstrapResult) apply(filenames fakejujuFilenames, controllerName string) error { |
1323 | + if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil { |
1324 | + return err |
1325 | + } |
1326 | + |
1327 | + logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile()) |
1328 | + if logsSource != "" && logsTarget != "" { |
1329 | + if err := os.Symlink(logsSource, logsTarget); err != nil { |
1330 | + return err |
1331 | + } |
1332 | + } |
1333 | + |
1334 | + if err := br.copyConfig(os.Getenv("JUJU_DATA"), controllerName); err != nil { |
1335 | + return err |
1336 | + } |
1337 | + |
1338 | + if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil { |
1339 | + return err |
1340 | + } |
1341 | + |
1342 | + return nil |
1343 | +} |
1344 | + |
1345 | +func (br bootstrapResult) copyConfig(targetCfgDir, controllerName string) error { |
1346 | + for _, name := range []string{"controllers.yaml", "models.yaml", "accounts.yaml"} { |
1347 | + source := filepath.Join(br.cfgdir, name) |
1348 | + target := filepath.Join(targetCfgDir, name) |
1349 | + |
1350 | + input, err := ioutil.ReadFile(source) |
1351 | + if err != nil { |
1352 | + return err |
1353 | + } |
1354 | + // Generated configuration by test fixtures has the controller name |
1355 | + // hard-coded to "kontroll". A simple replace should fix this for |
1356 | + // clients using this config and expecting a specific controller |
1357 | + // name. |
1358 | + output := strings.Replace(string(input), dummyControllerName, controllerName, -1) |
1359 | + err = ioutil.WriteFile(target, []byte(output), 0644) |
1360 | + if err != nil { |
1361 | + return err |
1362 | + } |
1363 | + } |
1364 | + |
1365 | + current := filepath.Join(targetCfgDir, "current-controller") |
1366 | + if err := ioutil.WriteFile(current, []byte(controllerName), 0644); err != nil { |
1367 | + return err |
1368 | + } |
1369 | + |
1370 | + return nil |
1371 | +} |
1372 | + |
1373 | +// See github.com/juju/juju/blob/juju/testing/conn.go. |
1374 | +const dummyControllerName = "kontroll" |
1375 | + |
1376 | +func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) { |
1377 | buffer := bufio.NewReader(stdout) |
1378 | + |
1379 | line, _, err := buffer.ReadLine() |
1380 | if err != nil { |
1381 | return nil, err |
1382 | } |
1383 | uuid := string(line) |
1384 | + |
1385 | line, _, err = buffer.ReadLine() |
1386 | if err != nil { |
1387 | return nil, err |
1388 | @@ -175,8 +383,8 @@ |
1389 | store := jujuclient.NewFileClientStore() |
1390 | // hard-coded value in juju testing |
1391 | // This will be replaced in JUJU_DATA copy of the juju client config. |
1392 | - currentController := "kontroll" |
1393 | - one, err := store.ControllerByName("kontroll") |
1394 | + currentController := dummyControllerName |
1395 | + one, err := store.ControllerByName(currentController) |
1396 | if err != nil { |
1397 | return nil, err |
1398 | } |
1399 | @@ -185,108 +393,17 @@ |
1400 | if err != nil { |
1401 | return nil, err |
1402 | } |
1403 | - apiInfo := &api.Info{ |
1404 | - Addrs: one.APIEndpoints, |
1405 | - Tag: names.NewUserTag(accountDetails.User), |
1406 | - Password: accountDetails.Password, |
1407 | - CACert: one.CACert, |
1408 | - ModelTag: names.NewModelTag(uuid), |
1409 | - } |
1410 | - |
1411 | - err = writeProcessInfo(envName, &processInfo{ |
1412 | - WorkDir: workDir, |
1413 | - EndpointAddr: one.APIEndpoints[0], |
1414 | - Uuid: uuid, |
1415 | - CACert: one.CACert, |
1416 | - }) |
1417 | - if err != nil { |
1418 | - return nil, err |
1419 | - } |
1420 | - return apiInfo, nil |
1421 | -} |
1422 | - |
1423 | -func readProcessInfo() (*processInfo, error) { |
1424 | - infoPath := filepath.Join(os.Getenv("JUJU_DATA"), "fakejuju") |
1425 | - data, err := ioutil.ReadFile(infoPath) |
1426 | - if err != nil { |
1427 | - return nil, err |
1428 | - } |
1429 | - info := &processInfo{} |
1430 | - err = goyaml.Unmarshal(data, info) |
1431 | - if err != nil { |
1432 | - return nil, err |
1433 | - } |
1434 | - return info, nil |
1435 | -} |
1436 | - |
1437 | -func writeProcessInfo(envName string, info *processInfo) error { |
1438 | - var err error |
1439 | - jujuHome := os.Getenv("JUJU_DATA") |
1440 | - infoPath := filepath.Join(jujuHome, "fakejuju") |
1441 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
1442 | - if logsDir == "" { |
1443 | - logsDir = jujuHome |
1444 | - } |
1445 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
1446 | - caCertPath := filepath.Join(jujuHome, "cert.ca") |
1447 | - data, _ := goyaml.Marshal(info) |
1448 | - if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" { |
1449 | - err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath) |
1450 | - if err != nil { |
1451 | - return err |
1452 | - } |
1453 | - } |
1454 | - |
1455 | - err = copyClientConfig( |
1456 | - filepath.Join(info.WorkDir, "controllers.yaml"), |
1457 | - filepath.Join(jujuHome, "controllers.yaml"), |
1458 | - envName) |
1459 | - if err != nil { |
1460 | - return err |
1461 | - } |
1462 | - err = copyClientConfig( |
1463 | - filepath.Join(info.WorkDir, "models.yaml"), |
1464 | - filepath.Join(jujuHome, "models.yaml"), |
1465 | - envName) |
1466 | - if err != nil { |
1467 | - return err |
1468 | - } |
1469 | - err = copyClientConfig( |
1470 | - filepath.Join(info.WorkDir, "accounts.yaml"), |
1471 | - filepath.Join(jujuHome, "accounts.yaml"), |
1472 | - envName) |
1473 | - if err != nil { |
1474 | - return err |
1475 | - } |
1476 | - err = ioutil.WriteFile( |
1477 | - filepath.Join(jujuHome, "current-controller"), |
1478 | - []byte(envName), 0644) |
1479 | - if err != nil { |
1480 | - return err |
1481 | - } |
1482 | - |
1483 | - err = ioutil.WriteFile(infoPath, data, 0644) |
1484 | - if err != nil { |
1485 | - return err |
1486 | - } |
1487 | - return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644) |
1488 | -} |
1489 | - |
1490 | -func copyClientConfig(src string, dst string, envName string) error { |
1491 | - input, err := ioutil.ReadFile(src) |
1492 | - if err != nil { |
1493 | - return err |
1494 | - } |
1495 | - // Generated configuration by test fixtures has the controller name |
1496 | - // hard-coded to "kontroll". A simple replace should fix this for |
1497 | - // clients using this config and expecting a specific controller |
1498 | - // name. |
1499 | - output := strings.Replace(string(input), "kontroll", envName, -1) |
1500 | - err = ioutil.WriteFile(dst, []byte(output), 0644) |
1501 | - if err != nil { |
1502 | - return err |
1503 | - } |
1504 | - return nil |
1505 | + |
1506 | + result := &bootstrapResult{ |
1507 | + dummyControllerName: dummyControllerName, |
1508 | + cfgdir: workDir, |
1509 | + uuid: uuid, |
1510 | + username: accountDetails.User, |
1511 | + password: accountDetails.Password, |
1512 | + addresses: one.APIEndpoints, |
1513 | + caCert: []byte(one.CACert), |
1514 | + } |
1515 | + return result, nil |
1516 | } |
1517 | |
1518 | // Read the failures info file pointed by the FAKE_JUJU_FAILURES environment |
1519 | @@ -326,12 +443,16 @@ |
1520 | return failuresInfo, nil |
1521 | } |
1522 | |
1523 | +//=================================================================== |
1524 | +// The fake-juju daemon (started by bootstrap) is found here. It is |
1525 | +// implemented as a test suite. |
1526 | + |
1527 | type FakeJujuSuite struct { |
1528 | jujutesting.JujuConnSuite |
1529 | |
1530 | instanceCount int |
1531 | machineStarted map[string]bool |
1532 | - fifoPath string |
1533 | + filenames fakejujuFilenames |
1534 | logFile *os.File |
1535 | } |
1536 | |
1537 | @@ -371,7 +492,7 @@ |
1538 | c.Assert(stateServer.SetProviderAddresses(address), gc.IsNil) |
1539 | now := time.Now() |
1540 | sInfo := states.StatusInfo{ |
1541 | - Status: states.StatusStarted, |
1542 | + Status: states.Started, |
1543 | Message: "", |
1544 | Since: &now, |
1545 | } |
1546 | @@ -398,20 +519,16 @@ |
1547 | c.Assert(err, gc.IsNil) |
1548 | os.Setenv("PATH", binPath+":"+os.Getenv("PATH")) |
1549 | |
1550 | - s.fifoPath = filepath.Join(jujuHome, "fifo") |
1551 | - syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0) |
1552 | + s.filenames = newFakeJujuFilenames("", "", jujuHome) |
1553 | + syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0) |
1554 | |
1555 | // Logging |
1556 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
1557 | - if logsDir == "" { |
1558 | - logsDir = jujuHome |
1559 | - } |
1560 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
1561 | + logPath := s.filenames.logsFile() |
1562 | s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
1563 | c.Assert(err, gc.IsNil) |
1564 | |
1565 | log.SetOutput(s.logFile) |
1566 | - log.Println("Started fake-juju at", jujuHome) |
1567 | + log.Println("Started fake-juju at ", jujuHome) |
1568 | |
1569 | } |
1570 | |
1571 | @@ -423,16 +540,30 @@ |
1572 | } |
1573 | |
1574 | func (s *FakeJujuSuite) TestStart(c *gc.C) { |
1575 | + fifoPath := s.filenames.fifoFile() |
1576 | watcher := s.State.Watch() |
1577 | go func() { |
1578 | - log.Println("Open commands FIFO", s.fifoPath) |
1579 | - fd, err := os.Open(s.fifoPath) |
1580 | + log.Println("Open commands FIFO", fifoPath) |
1581 | + fd, err := os.Open(fifoPath) |
1582 | if err != nil { |
1583 | log.Println("Failed to open commands FIFO") |
1584 | } |
1585 | c.Assert(err, gc.IsNil) |
1586 | + defer func() { |
1587 | + if err := fd.Close(); err != nil { |
1588 | + c.Logf("failed closing FIFO file: %s", err) |
1589 | + } |
1590 | + // Mark the controller as destroyed by renaming some files. |
1591 | + if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil { |
1592 | + c.Logf("failed renaming FIFO file: %s", err) |
1593 | + } |
1594 | + infofile := s.filenames.infoFile() |
1595 | + if err := os.Rename(infofile, infofile+".destroyed"); err != nil { |
1596 | + c.Logf("failed renaming info file: %s", err) |
1597 | + } |
1598 | + }() |
1599 | scanner := bufio.NewScanner(fd) |
1600 | - log.Println("Listen for commands on FIFO", s.fifoPath) |
1601 | + log.Println("Listen for commands on FIFO", fifoPath) |
1602 | scanner.Scan() |
1603 | log.Println("Stopping fake-juju") |
1604 | watcher.Stop() |
1605 | @@ -495,12 +626,12 @@ |
1606 | } |
1607 | status, _ := machine.Status() |
1608 | log.Println("Machine has status:", string(status.Status), status.Message) |
1609 | - if status.Status == states.StatusPending { |
1610 | + if status.Status == states.Pending { |
1611 | if err = s.startMachine(machine); err != nil { |
1612 | log.Println("Got error with startMachine:", err) |
1613 | return err |
1614 | } |
1615 | - } else if status.Status == states.StatusStarted { |
1616 | + } else if status.Status == states.Started { |
1617 | log.Println("Starting units on machine", id) |
1618 | if _, ok := s.machineStarted[id]; !ok { |
1619 | s.machineStarted[id] = true |
1620 | @@ -531,19 +662,19 @@ |
1621 | return err |
1622 | } |
1623 | machineStatus, _ := machine.Status() |
1624 | - if machineStatus.Status != states.StatusStarted { |
1625 | + if machineStatus.Status != states.Started { |
1626 | return nil |
1627 | } |
1628 | status, _ := unit.Status() |
1629 | log.Println("Unit has status", string(status.Status), status.Message) |
1630 | - if status.Status != states.StatusActive && status.Status != states.StatusError { |
1631 | + if status.Status != states.Active && status.Status != states.Error { |
1632 | log.Println("Start unit", id) |
1633 | err = s.startUnit(unit) |
1634 | if err != nil { |
1635 | log.Println("Got error changing unit status", id, err) |
1636 | return err |
1637 | } |
1638 | - } else if status.Status != states.StatusError { |
1639 | + } else if status.Status != states.Error { |
1640 | failuresInfo, err := readFailuresInfo() |
1641 | if err != nil { |
1642 | return err |
1643 | @@ -554,7 +685,7 @@ |
1644 | log.Println("Got error checking agent status", id, err) |
1645 | return err |
1646 | } |
1647 | - if agentStatus.Status != states.StatusError { |
1648 | + if agentStatus.Status != states.Error { |
1649 | log.Println("Error unit", id) |
1650 | err = s.errorUnit(unit) |
1651 | if err != nil { |
1652 | @@ -571,7 +702,7 @@ |
1653 | time.Sleep(500 * time.Millisecond) |
1654 | now := time.Now() |
1655 | sInfo := states.StatusInfo{ |
1656 | - Status: states.StatusStarted, |
1657 | + Status: states.Started, |
1658 | Message: "", |
1659 | Since: &now, |
1660 | } |
1661 | @@ -604,7 +735,7 @@ |
1662 | time.Sleep(500 * time.Millisecond) |
1663 | now := time.Now() |
1664 | sInfo := states.StatusInfo{ |
1665 | - Status: states.StatusError, |
1666 | + Status: states.Error, |
1667 | Message: "machine errored", |
1668 | Since: &now, |
1669 | } |
1670 | @@ -623,7 +754,7 @@ |
1671 | return nil |
1672 | for _, unit := range units { |
1673 | unitStatus, _ := unit.Status() |
1674 | - if unitStatus.Status != states.StatusActive { |
1675 | + if unitStatus.Status != states.Active { |
1676 | if err = s.startUnit(unit); err != nil { |
1677 | return err |
1678 | } |
1679 | @@ -635,7 +766,7 @@ |
1680 | func (s *FakeJujuSuite) startUnit(unit *state.Unit) error { |
1681 | now := time.Now() |
1682 | sInfo := states.StatusInfo{ |
1683 | - Status: states.StatusStarted, |
1684 | + Status: states.Started, |
1685 | Message: "", |
1686 | Since: &now, |
1687 | } |
1688 | @@ -653,7 +784,7 @@ |
1689 | return err |
1690 | } |
1691 | idleInfo := states.StatusInfo{ |
1692 | - Status: states.StatusIdle, |
1693 | + Status: states.Idle, |
1694 | Message: "", |
1695 | Since: &now, |
1696 | } |
1697 | @@ -668,7 +799,7 @@ |
1698 | log.Println("Erroring unit", unit.Name()) |
1699 | now := time.Now() |
1700 | sInfo := states.StatusInfo{ |
1701 | - Status: states.StatusIdle, |
1702 | + Status: states.Idle, |
1703 | Message: "unit errored", |
1704 | Since: &now, |
1705 | } |
1706 | |
1707 | === modified file 'Makefile' |
1708 | --- Makefile 2016-09-20 18:26:47 +0000 |
1709 | +++ Makefile 2016-10-25 17:25:06 +0000 |
1710 | @@ -11,7 +11,7 @@ |
1711 | INSTALLDIR = $(DESTDIR)/usr/bin |
1712 | INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION) |
1713 | |
1714 | -$(JUJU_VERSION)/$(JUJU_VERSION): |
1715 | +$(JUJU_VERSION)/$(JUJU_VERSION): $(JUJU_VERSION)/fake-juju.go |
1716 | case $(JUJU_VERSION) in \ |
1717 | 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\ |
1718 | 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\ |
1719 | @@ -59,8 +59,8 @@ |
1720 | else ########################################### |
1721 | # for all versions |
1722 | |
1723 | -JUJU1_VERSIONS = 1.24.7 1.25.6 |
1724 | -JUJU2_VERSIONS = 2.0-beta17 |
1725 | +JUJU1_VERSIONS = 1.25.6 |
1726 | +JUJU2_VERSIONS = 2.0.0 |
1727 | VERSIONS = $(JUJU1_VERSIONS) $(JUJU2_VERSIONS) |
1728 | BUILT_VERSIONS = $(foreach version,$(VERSIONS),$(version)/$(version)) |
1729 | |
1730 | |
1731 | === removed file 'patches/juju-core_1.24.7.patch' |
1732 | --- patches/juju-core_1.24.7.patch 2016-03-18 11:10:52 +0000 |
1733 | +++ patches/juju-core_1.24.7.patch 1970-01-01 00:00:00 +0000 |
1734 | @@ -1,47 +0,0 @@ |
1735 | ---- 1.24.7/src/github.com/juju/juju/testcharms/charm.go.orig 2015-06-24 12:02:02.746416146 +0200 |
1736 | -+++ 1.24.7/src/github.com/juju/juju/testcharms/charm.go 2015-06-24 12:03:49.810418650 +0200 |
1737 | -@@ -10,4 +10,6 @@ |
1738 | - ) |
1739 | - |
1740 | - // Repo provides access to the test charm repository. |
1741 | --var Repo = testing.NewRepo("charm-repo", "quantal") |
1742 | -+// XXX fake-juju: avoid crashing because the charm-repo dir is not there |
1743 | -+//var Repo = testing.NewRepo("charm-repo", "quantal") |
1744 | -+var Repo = &testing.Repo{} |
1745 | - |
1746 | ---- 1.24.7/src/github.com/juju/juju/provider/dummy/environs.go.orig 2015-07-06 15:01:14.200568258 +0200 |
1747 | -+++ 1.24.7/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +0200 |
1748 | -@@ -642,9 +642,9 @@ |
1749 | - |
1750 | - // PrecheckInstance is specified in the state.Prechecker interface. |
1751 | - func (*environ) PrecheckInstance(series string, cons constraints.Value, placement string) error { |
1752 | -- if placement != "" && placement != "valid" { |
1753 | -- return fmt.Errorf("%s placement is invalid", placement) |
1754 | -- } |
1755 | -+// if placement != "" && placement != "valid" { |
1756 | -+// return fmt.Errorf("%s placement is invalid", placement) |
1757 | -+// } |
1758 | - return nil |
1759 | - } |
1760 | - |
1761 | ---- 1.24.7/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +0000 |
1762 | -+++ 1.24.7/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +0000 |
1763 | -@@ -52,7 +52,7 @@ |
1764 | - } |
1765 | - |
1766 | - func mustNewCA() (string, string) { |
1767 | -- cert.KeyBits = 512 |
1768 | -+ cert.KeyBits = 1024 |
1769 | - caCert, caKey, err := cert.NewCA("juju testing", time.Now().AddDate(10, 0, 0)) |
1770 | - if err != nil { |
1771 | - panic(err) |
1772 | -@@ -61,7 +61,7 @@ |
1773 | - } |
1774 | - |
1775 | - func mustNewServer() (string, string) { |
1776 | -- cert.KeyBits = 512 |
1777 | -+ cert.KeyBits = 1024 |
1778 | - var hostnames []string |
1779 | - srvCert, srvKey, err := cert.NewServer(CACert, CAKey, time.Now().AddDate(10, 0, 0), hostnames) |
1780 | - if err != nil { |
1781 | - |
1782 | |
1783 | === renamed file 'patches/juju-core_2.0-beta17.patch' => 'patches/juju-core_2.0.0.patch' |
1784 | --- patches/juju-core_2.0-beta17.patch 2016-09-01 22:03:22 +0000 |
1785 | +++ patches/juju-core_2.0.0.patch 2016-10-25 17:25:06 +0000 |
1786 | @@ -1,5 +1,5 @@ |
1787 | ---- 2.0-beta17/src/github.com/juju/juju/testcharms/charm.go 2016-03-10 13:45:57.000000000 +0100 |
1788 | -+++ 2.0-beta17/src/github.com/juju/juju/testcharms/charm.go 2016-03-21 10:46:24.312966629 +0100 |
1789 | +--- 2.0.0/src/github.com/juju/juju/testcharms/charm.go 2016-03-10 13:45:57.000000000 +0100 |
1790 | ++++ 2.0.0/src/github.com/juju/juju/testcharms/charm.go 2016-03-21 10:46:24.312966629 +0100 |
1791 | @@ -17,7 +17,9 @@ |
1792 | ) |
1793 | |
1794 | @@ -11,8 +11,8 @@ |
1795 | |
1796 | // UploadCharm uploads a charm using the given charm store client, and returns |
1797 | // the resulting charm URL and charm. |
1798 | ---- 2.0-beta17/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:01:14.200568258 +0200 |
1799 | -+++ 2.0-beta17/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +0200 |
1800 | +--- 2.0.0/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:01:14.200568258 +0200 |
1801 | ++++ 2.0.0/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +0200 |
1802 | @@ -633,9 +633,9 @@ |
1803 | |
1804 | // PrecheckInstance is specified in the state.Prechecker interface. |
1805 | @@ -26,8 +26,8 @@ |
1806 | return nil |
1807 | } |
1808 | |
1809 | ---- 2.0-beta17/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +0000 |
1810 | -+++ 2.0-beta17/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +0000 |
1811 | +--- 2.0.0/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +0000 |
1812 | ++++ 2.0.0/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +0000 |
1813 | @@ -52,7 +52,7 @@ |
1814 | } |
1815 | |
1816 | |
1817 | === modified file 'python/Makefile' |
1818 | --- python/Makefile 2016-10-06 21:44:31 +0000 |
1819 | +++ python/Makefile 2016-10-25 17:25:06 +0000 |
1820 | @@ -6,4 +6,4 @@ |
1821 | |
1822 | .PHONY: install-dev |
1823 | install-dev: |
1824 | - ln -s $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju |
1825 | + ln -snv $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju |
1826 | |
1827 | === modified file 'python/fakejuju/fakejuju.py' |
1828 | --- python/fakejuju/fakejuju.py 2016-10-17 15:54:59 +0000 |
1829 | +++ python/fakejuju/fakejuju.py 2016-10-25 17:25:06 +0000 |
1830 | @@ -2,6 +2,7 @@ |
1831 | |
1832 | import os.path |
1833 | |
1834 | +import txjuju |
1835 | import txjuju.cli |
1836 | |
1837 | from .failures import Failures |
1838 | @@ -23,15 +24,17 @@ |
1839 | return os.path.join(bindir, filename) |
1840 | |
1841 | |
1842 | -def set_envvars(envvars, failures_filename=None, logsdir=None): |
1843 | +def set_envvars(envvars, datadir=None, failures_filename=None, logsdir=None): |
1844 | """Return the environment variables with which to run fake-juju. |
1845 | |
1846 | @param envvars: The env dict to update. |
1847 | + @param datadir: The fake-juju data directory. |
1848 | @param failures_filename: The path to the failures file that |
1849 | fake-juju will use. |
1850 | @params logsdir: The path to the directory where fake-juju will |
1851 | write its log files. |
1852 | """ |
1853 | + envvars["FAKE_JUJU_DATA_DIR"] = datadir or "" |
1854 | envvars["FAKE_JUJU_FAILURES"] = failures_filename or "" |
1855 | envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or "" |
1856 | |
1857 | @@ -40,46 +43,47 @@ |
1858 | """The fundamental details for fake-juju.""" |
1859 | |
1860 | @classmethod |
1861 | - def from_version(cls, version, cfgdir, |
1862 | + def from_version(cls, version, datadir, |
1863 | logsdir=None, failuresdir=None, bindir=None): |
1864 | """Return a new instance given the provided information. |
1865 | |
1866 | @param version: The Juju version to fake. |
1867 | - @param cfgdir: The "juju home" directory to use. |
1868 | + @param datadir: The directory in which to store files specific |
1869 | + to fake-juju. |
1870 | @param logsdir: The directory where logs will be written. |
1871 | - This defaults to cfgdir. |
1872 | + This defaults to datadir. |
1873 | @params failuresdir: The directory where failure injection |
1874 | is managed. |
1875 | @param bindir: The directory containing the fake-juju binary. |
1876 | This defaults to /usr/bin. |
1877 | """ |
1878 | - if logsdir is None: |
1879 | - logsdir = cfgdir |
1880 | if failuresdir is None: |
1881 | - failuresdir = cfgdir |
1882 | + failuresdir = datadir |
1883 | filename = get_filename(version, bindir=bindir) |
1884 | failures = Failures(failuresdir) |
1885 | - return cls(filename, version, cfgdir, logsdir, failures) |
1886 | + return cls(filename, version, datadir, logsdir, failures) |
1887 | |
1888 | - def __init__(self, filename, version, cfgdir, logsdir=None, failures=None): |
1889 | + def __init__(self, filename, version, datadir, |
1890 | + logsdir=None, failures=None): |
1891 | """ |
1892 | @param filename: The path to the fake-juju binary. |
1893 | @param version: The Juju version to fake. |
1894 | - @param cfgdir: The "juju home" directory to use. |
1895 | + @param datadir: The directory in which to store files specific |
1896 | + to fake-juju. |
1897 | @param logsdir: The directory where logs will be written. |
1898 | - This defaults to cfgdir. |
1899 | + This defaults to datadir. |
1900 | @param failures: The set of fake-juju failures to use. |
1901 | """ |
1902 | - logsdir = logsdir if logsdir is not None else cfgdir |
1903 | - if failures is None and cfgdir: |
1904 | - failures = Failures(cfgdir) |
1905 | + logsdir = logsdir if logsdir is not None else datadir |
1906 | + if failures is None and datadir: |
1907 | + failures = Failures(datadir) |
1908 | |
1909 | if not filename: |
1910 | raise ValueError("missing filename") |
1911 | if not version: |
1912 | raise ValueError("missing version") |
1913 | - if not cfgdir: |
1914 | - raise ValueError("missing cfgdir") |
1915 | + if not datadir: |
1916 | + raise ValueError("missing datadir") |
1917 | if not logsdir: |
1918 | raise ValueError("missing logsdir") |
1919 | if failures is None: |
1920 | @@ -87,7 +91,7 @@ |
1921 | |
1922 | self.filename = filename |
1923 | self.version = version |
1924 | - self.cfgdir = cfgdir |
1925 | + self.datadir = datadir |
1926 | self.logsdir = logsdir |
1927 | self.failures = failures |
1928 | |
1929 | @@ -99,19 +103,19 @@ |
1930 | @property |
1931 | def infofile(self): |
1932 | """The path to fake-juju's data cache.""" |
1933 | - return os.path.join(self.cfgdir, "fakejuju") |
1934 | + return os.path.join(self.datadir, "fakejuju") |
1935 | |
1936 | @property |
1937 | def fifo(self): |
1938 | """The path to the fifo file that triggers shutdown.""" |
1939 | - return os.path.join(self.cfgdir, "fifo") |
1940 | + return os.path.join(self.datadir, "fifo") |
1941 | |
1942 | @property |
1943 | def cacertfile(self): |
1944 | """The path to the API server's certificate.""" |
1945 | - return os.path.join(self.cfgdir, "cert.ca") |
1946 | + return os.path.join(self.datadir, "cert.ca") |
1947 | |
1948 | - def cli(self, envvars=None): |
1949 | + def cli(self, cfgdir, envvars=None): |
1950 | """Return the txjuju.cli.CLI for this fake-juju. |
1951 | |
1952 | Currently fake-juju supports only the following juju subcommands: |
1953 | @@ -123,10 +127,27 @@ |
1954 | Note that passwords are always omited, even if requested. |
1955 | * api-endpoints |
1956 | * destroy-environment |
1957 | + |
1958 | + Note that fake-juju ignores local config files. |
1959 | """ |
1960 | if envvars is None: |
1961 | envvars = os.environ |
1962 | envvars = dict(envvars) |
1963 | - set_envvars(envvars, self.failures._filename, self.logsdir) |
1964 | + set_envvars( |
1965 | + envvars, self.datadir, self.failures._filename, self.logsdir) |
1966 | return txjuju.cli.CLI.from_version( |
1967 | - self.filename, self.version, self.cfgdir, envvars) |
1968 | + self.filename, self.version, cfgdir, envvars) |
1969 | + |
1970 | + def bootstrap(self, name, cfgdir, admin_secret=None): |
1971 | + """Return the CLI and APIInfo after bootstrapping from scratch.""" |
1972 | + from . import get_bootstrap_spec |
1973 | + spec = get_bootstrap_spec(name, admin_secret) |
1974 | + cfgfile = txjuju.prepare_for_bootstrap(spec, self.version, cfgdir) |
1975 | + cli = self.cli(cfgdir) |
1976 | + cli.bootstrap(spec, cfgfile=cfgfile) |
1977 | + api_info = cli.api_info(spec.name) |
1978 | + return cli, api_info |
1979 | + |
1980 | + def is_bootstrapped(self): |
1981 | + """Return True if a fake-juju controller is running.""" |
1982 | + return os.path.exists(self.fifo) |
1983 | |
1984 | === modified file 'python/fakejuju/testing.py' |
1985 | --- python/fakejuju/testing.py 2016-10-06 22:51:41 +0000 |
1986 | +++ python/fakejuju/testing.py 2016-10-25 17:25:06 +0000 |
1987 | @@ -1,6 +1,5 @@ |
1988 | # Copyright 2016 Canonical Limited. All rights reserved. |
1989 | |
1990 | -import txjuju |
1991 | from fixtures import Fixture, TempDir |
1992 | from testtools.content import content_from_file |
1993 | |
1994 | @@ -8,7 +7,7 @@ |
1995 | |
1996 | |
1997 | JUJU1_VER = "1.25.6" |
1998 | -JUJU2_VER = "2.0-beta17" |
1999 | +JUJU2_VER = "2.0.0" |
2000 | JUJU_VER = JUJU1_VER |
2001 | |
2002 | |
2003 | @@ -40,29 +39,16 @@ |
2004 | def setUp(self): |
2005 | super(FakeJujuFixture, self).setUp() |
2006 | self._juju_home = self.useFixture(TempDir()) |
2007 | - self._juju = fakejuju.FakeJuju.make( |
2008 | - self._juju_home.path, self._version, self._logs_dir) |
2009 | + self.fakejuju = fakejuju.FakeJuju.from_version( |
2010 | + self._version, self._juju_home.path, self._logs_dir) |
2011 | |
2012 | if not self._logs_dir: |
2013 | # Attach logs as testtools details. |
2014 | self.addDetail("log-file", content_from_file(self._juju.logfile)) |
2015 | |
2016 | - spec = fakejuju.get_bootstrap_spec(self._controller, self._password) |
2017 | - cfgfile = txjuju.prepare_for_bootstrap( |
2018 | - spec, self._version, self._juju_home) |
2019 | - cli = self._juju.cli() |
2020 | - cli.bootstrap(spec, cfgfile=cfgfile) |
2021 | - api_info = cli.api_info(spec.name) |
2022 | - if self._version.startswith("1."): |
2023 | - # fake-juju doesn't give us the password, so we have to |
2024 | - # set it here. |
2025 | - api_info = api_info._replace(password=self._password) |
2026 | - self.api_info = api_info |
2027 | + self._juju, self.all_api_info = self.fakejuju.bootstrap( |
2028 | + self._controller, self._password) |
2029 | |
2030 | def cleanUp(self): |
2031 | self._juju.destroy_controller(self._controller) |
2032 | super(FakeJujuFixture, self).cleanUp() |
2033 | - |
2034 | - def add_failure(self, entity): |
2035 | - """Make the given entity fail with an error status.""" |
2036 | - self._juju.failures.fail_entity(entity) |
2037 | |
2038 | === modified file 'python/fakejuju/tests/test_fakejuju.py' |
2039 | --- python/fakejuju/tests/test_fakejuju.py 2016-10-17 15:36:15 +0000 |
2040 | +++ python/fakejuju/tests/test_fakejuju.py 2016-10-25 17:25:06 +0000 |
2041 | @@ -1,10 +1,16 @@ |
2042 | # Copyright 2016 Canonical Limited. All rights reserved. |
2043 | |
2044 | +from contextlib import contextmanager |
2045 | +import json |
2046 | import os |
2047 | +import shutil |
2048 | +import tempfile |
2049 | import unittest |
2050 | |
2051 | from txjuju import _juju1, _juju2 |
2052 | from txjuju._utils import Executable |
2053 | +import txjuju.cli |
2054 | +import yaml |
2055 | |
2056 | from fakejuju.failures import Failures |
2057 | from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju |
2058 | @@ -44,9 +50,10 @@ |
2059 | def test_all_args(self): |
2060 | """set_envvars() works correctly when given all args.""" |
2061 | envvars = {} |
2062 | - set_envvars(envvars, "/spam/failures", "/eggs/logsdir") |
2063 | + set_envvars(envvars, "/spam", "/spam/failures", "/eggs/logsdir") |
2064 | |
2065 | self.assertEqual(envvars, { |
2066 | + "FAKE_JUJU_DATA_DIR": "/spam", |
2067 | "FAKE_JUJU_FAILURES": "/spam/failures", |
2068 | "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir", |
2069 | }) |
2070 | @@ -57,6 +64,7 @@ |
2071 | set_envvars(envvars) |
2072 | |
2073 | self.assertEqual(envvars, { |
2074 | + "FAKE_JUJU_DATA_DIR": "", |
2075 | "FAKE_JUJU_FAILURES": "", |
2076 | "FAKE_JUJU_LOGS_DIR": "", |
2077 | }) |
2078 | @@ -64,9 +72,10 @@ |
2079 | def test_start_empty(self): |
2080 | """set_envvars() sets all values on an empty dict.""" |
2081 | envvars = {} |
2082 | - set_envvars(envvars, "x", "y") |
2083 | + set_envvars(envvars, "w", "x", "y") |
2084 | |
2085 | self.assertEqual(envvars, { |
2086 | + "FAKE_JUJU_DATA_DIR": "w", |
2087 | "FAKE_JUJU_FAILURES": "x", |
2088 | "FAKE_JUJU_LOGS_DIR": "y", |
2089 | }) |
2090 | @@ -74,10 +83,11 @@ |
2091 | def test_no_collisions(self): |
2092 | """set_envvars() sets all values when none are set yet.""" |
2093 | envvars = {"SPAM": "eggs"} |
2094 | - set_envvars(envvars, "x", "y") |
2095 | + set_envvars(envvars, "w", "x", "y") |
2096 | |
2097 | self.assertEqual(envvars, { |
2098 | "SPAM": "eggs", |
2099 | + "FAKE_JUJU_DATA_DIR": "w", |
2100 | "FAKE_JUJU_FAILURES": "x", |
2101 | "FAKE_JUJU_LOGS_DIR": "y", |
2102 | }) |
2103 | @@ -85,12 +95,14 @@ |
2104 | def test_empty_to_nonempty(self): |
2105 | """set_envvars() updates empty values.""" |
2106 | envvars = { |
2107 | + "FAKE_JUJU_DATA_DIR": "", |
2108 | "FAKE_JUJU_FAILURES": "", |
2109 | "FAKE_JUJU_LOGS_DIR": "", |
2110 | } |
2111 | - set_envvars(envvars, "x", "y") |
2112 | + set_envvars(envvars, "w", "x", "y") |
2113 | |
2114 | self.assertEqual(envvars, { |
2115 | + "FAKE_JUJU_DATA_DIR": "w", |
2116 | "FAKE_JUJU_FAILURES": "x", |
2117 | "FAKE_JUJU_LOGS_DIR": "y", |
2118 | }) |
2119 | @@ -98,12 +110,14 @@ |
2120 | def test_nonempty_to_nonempty(self): |
2121 | """set_envvars() overwrites existing values.""" |
2122 | envvars = { |
2123 | + "FAKE_JUJU_DATA_DIR": "spam", |
2124 | "FAKE_JUJU_FAILURES": "spam", |
2125 | "FAKE_JUJU_LOGS_DIR": "ham", |
2126 | } |
2127 | - set_envvars(envvars, "x", "y") |
2128 | + set_envvars(envvars, "w", "x", "y") |
2129 | |
2130 | self.assertEqual(envvars, { |
2131 | + "FAKE_JUJU_DATA_DIR": "w", |
2132 | "FAKE_JUJU_FAILURES": "x", |
2133 | "FAKE_JUJU_LOGS_DIR": "y", |
2134 | }) |
2135 | @@ -111,12 +125,14 @@ |
2136 | def test_nonempty_to_empty(self): |
2137 | """set_envvars() with no args "unsets" existing values.""" |
2138 | envvars = { |
2139 | + "FAKE_JUJU_DATA_DIR": "w", |
2140 | "FAKE_JUJU_FAILURES": "x", |
2141 | "FAKE_JUJU_LOGS_DIR": "y", |
2142 | } |
2143 | set_envvars(envvars) |
2144 | |
2145 | self.assertEqual(envvars, { |
2146 | + "FAKE_JUJU_DATA_DIR": "", |
2147 | "FAKE_JUJU_FAILURES": "", |
2148 | "FAKE_JUJU_LOGS_DIR": "", |
2149 | }) |
2150 | @@ -131,7 +147,7 @@ |
2151 | |
2152 | self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6") |
2153 | self.assertEqual(juju.version, "1.25.6") |
2154 | - self.assertEqual(juju.cfgdir, "/a/juju/home") |
2155 | + self.assertEqual(juju.datadir, "/a/juju/home") |
2156 | self.assertEqual(juju.logsdir, "/logs/dir") |
2157 | self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures") |
2158 | |
2159 | @@ -141,19 +157,20 @@ |
2160 | |
2161 | self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6") |
2162 | self.assertEqual(juju.version, "1.25.6") |
2163 | - self.assertEqual(juju.cfgdir, "/my/juju/home") |
2164 | + self.assertEqual(juju.datadir, "/my/juju/home") |
2165 | self.assertEqual(juju.logsdir, "/my/juju/home") |
2166 | self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures") |
2167 | |
2168 | def test_full(self): |
2169 | """FakeJuju() works correctly when given all args.""" |
2170 | - cfgdir = "/my/juju/home" |
2171 | - failures = Failures(cfgdir) |
2172 | - juju = FakeJuju("/fake-juju", "1.25.6", cfgdir, "/some/logs", failures) |
2173 | + datadir = "/my/juju/home" |
2174 | + failures = Failures(datadir) |
2175 | + juju = FakeJuju( |
2176 | + "/fake-juju", "1.25.6", datadir, "/some/logs", failures) |
2177 | |
2178 | self.assertEqual(juju.filename, "/fake-juju") |
2179 | self.assertEqual(juju.version, "1.25.6") |
2180 | - self.assertEqual(juju.cfgdir, cfgdir) |
2181 | + self.assertEqual(juju.datadir, datadir) |
2182 | self.assertEqual(juju.logsdir, "/some/logs") |
2183 | self.assertIs(juju.failures, failures) |
2184 | |
2185 | @@ -163,7 +180,7 @@ |
2186 | |
2187 | self.assertEqual(juju.filename, "/fake-juju") |
2188 | self.assertEqual(juju.version, "1.25.6") |
2189 | - self.assertEqual(juju.cfgdir, "/my/juju/home") |
2190 | + self.assertEqual(juju.datadir, "/my/juju/home") |
2191 | self.assertEqual(juju.logsdir, "/my/juju/home") |
2192 | self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures") |
2193 | |
2194 | @@ -174,7 +191,7 @@ |
2195 | juju_unicode = FakeJuju( |
2196 | u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/...")) |
2197 | |
2198 | - for name in ('filename version cfgdir logsdir'.split()): |
2199 | + for name in ('filename version datadir logsdir'.split()): |
2200 | self.assertIsInstance(getattr(juju_str, name), str) |
2201 | self.assertIsInstance(getattr(juju_unicode, name), unicode) |
2202 | |
2203 | @@ -192,8 +209,8 @@ |
2204 | with self.assertRaises(ValueError): |
2205 | FakeJuju("/fake-juju", "", "/my/juju/home") |
2206 | |
2207 | - def test_missing_cfgdir(self): |
2208 | - """FakeJuju() fails if cfgdir is None or empty.""" |
2209 | + def test_missing_datadir(self): |
2210 | + """FakeJuju() fails if datadir is None or empty.""" |
2211 | with self.assertRaises(ValueError): |
2212 | FakeJuju("/fake-juju", "1.25.6", None) |
2213 | with self.assertRaises(ValueError): |
2214 | @@ -226,44 +243,208 @@ |
2215 | def test_cli_full(self): |
2216 | """FakeJuju.cli() works correctly when given all args.""" |
2217 | juju = FakeJuju("/fake-juju", "1.25.6", "/x") |
2218 | - cli = juju.cli({"SPAM": "eggs"}) |
2219 | + cli = juju.cli("/y", {"SPAM": "eggs"}) |
2220 | |
2221 | self.assertEqual( |
2222 | cli._exe, |
2223 | Executable("/fake-juju", { |
2224 | "SPAM": "eggs", |
2225 | + "FAKE_JUJU_DATA_DIR": "/x", |
2226 | "FAKE_JUJU_FAILURES": "/x/juju-failures", |
2227 | "FAKE_JUJU_LOGS_DIR": "/x", |
2228 | - "JUJU_HOME": "/x", |
2229 | + "JUJU_HOME": "/y", |
2230 | }), |
2231 | ) |
2232 | |
2233 | def test_cli_minimal(self): |
2234 | """FakeJuju.cli() works correctly when given minimal args.""" |
2235 | juju = FakeJuju("/fake-juju", "1.25.6", "/x") |
2236 | - cli = juju.cli() |
2237 | + cli = juju.cli("/y") |
2238 | |
2239 | self.assertEqual( |
2240 | cli._exe, |
2241 | Executable("/fake-juju", dict(os.environ, **{ |
2242 | + "FAKE_JUJU_DATA_DIR": "/x", |
2243 | "FAKE_JUJU_FAILURES": "/x/juju-failures", |
2244 | "FAKE_JUJU_LOGS_DIR": "/x", |
2245 | - "JUJU_HOME": "/x", |
2246 | + "JUJU_HOME": "/y", |
2247 | })), |
2248 | ) |
2249 | |
2250 | def test_cli_juju1(self): |
2251 | """FakeJuju.cli() works correctly for Juju 1.x.""" |
2252 | juju = FakeJuju.from_version("1.25.6", "/x") |
2253 | - cli = juju.cli() |
2254 | + cli = juju.cli("/y") |
2255 | |
2256 | - self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/x") |
2257 | + self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/y") |
2258 | self.assertIsInstance(cli._juju, _juju1.CLIHooks) |
2259 | |
2260 | def test_cli_juju2(self): |
2261 | """FakeJuju.cli() works correctly for Juju 2.x.""" |
2262 | juju = FakeJuju.from_version("2.0.0", "/x") |
2263 | - cli = juju.cli() |
2264 | + cli = juju.cli("/y") |
2265 | |
2266 | - self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/x") |
2267 | + self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/y") |
2268 | self.assertIsInstance(cli._juju, _juju2.CLIHooks) |
2269 | + |
2270 | + def test_bootstrap(self): |
2271 | + """FakeJuju.bootstrap() bootstraps from scratch using fake-juju.""" |
2272 | + expected = txjuju.cli.APIInfo( |
2273 | + endpoints=['localhost:12727'], |
2274 | + user='admin', |
2275 | + password='dummy-secret', |
2276 | + model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d', |
2277 | + ) |
2278 | + version = "1.25.6" |
2279 | + with tempdir() as testdir: |
2280 | + bindir = os.path.join(testdir, "bin") |
2281 | + datadir = os.path.join(testdir, "fakejuju") |
2282 | + cfgdir = os.path.join(testdir, ".juju") |
2283 | + |
2284 | + logfilename = write_fakejuju_script( |
2285 | + version, bindir, datadir, cfgdir, expected) |
2286 | + fakejuju = FakeJuju.from_version(version, cfgdir, bindir=bindir) |
2287 | + |
2288 | + cli, api_info = fakejuju.bootstrap("spam", cfgdir, "secret") |
2289 | + |
2290 | + files = [] |
2291 | + files.extend(os.path.join(os.path.basename(datadir), name) |
2292 | + for name in os.listdir(datadir)) |
2293 | + files.extend(os.path.join(os.path.basename(cfgdir), name) |
2294 | + for name in os.listdir(cfgdir)) |
2295 | + with open(os.path.join(cfgdir, "environments.yaml")) as envfile: |
2296 | + data = envfile.read() |
2297 | + |
2298 | + cli.destroy_controller() |
2299 | + with open(logfilename) as logfile: |
2300 | + calls = [line.strip() for line in logfile] |
2301 | + |
2302 | + self.maxDiff = None |
2303 | + self.assertEqual(api_info, { |
2304 | + 'controller': expected, |
2305 | + None: expected._replace(model_uuid=None), |
2306 | + }) |
2307 | + subcommands = [] |
2308 | + for call in calls: |
2309 | + args = call.split() |
2310 | + self.assertEqual(os.path.basename(args[0]), "fake-juju-" + version) |
2311 | + subcommands.append(args[1]) |
2312 | + self.assertEqual(subcommands, [ |
2313 | + "bootstrap", |
2314 | + "api-info", |
2315 | + "destroy-environment", |
2316 | + ]) |
2317 | + self.assertItemsEqual(files, [ |
2318 | + '.juju/environments', |
2319 | + '.juju/environments.yaml', |
2320 | + 'fakejuju/cert.ca', |
2321 | + 'fakejuju/fake-juju.log', |
2322 | + 'fakejuju/fakejuju', |
2323 | + 'fakejuju/fifo', |
2324 | + ]) |
2325 | + self.assertEqual(yaml.load(data), { |
2326 | + "environments": { |
2327 | + "spam": { |
2328 | + "admin-secret": "secret", |
2329 | + "default-series": "trusty", |
2330 | + "type": "dummy", |
2331 | + }, |
2332 | + }, |
2333 | + }) |
2334 | + |
2335 | + def test_is_bootstrapped_true(self): |
2336 | + """FakeJuju.is_bootstrapped() returns True if the fifo file exists.""" |
2337 | + with tempdir() as datadir: |
2338 | + fakejuju = FakeJuju.from_version("1.25.6", datadir) |
2339 | + with open(fakejuju.fifo, "w"): |
2340 | + pass |
2341 | + result = fakejuju.is_bootstrapped() |
2342 | + |
2343 | + self.assertTrue(result) |
2344 | + |
2345 | + def test_is_bootstrapped_false(self): |
2346 | + """FakeJuju.is_bootstrapped() returns False if the fifo is gone.""" |
2347 | + with tempdir() as datadir: |
2348 | + fakejuju = FakeJuju.from_version("1.25.6", datadir) |
2349 | + result = fakejuju.is_bootstrapped() |
2350 | + |
2351 | + self.assertFalse(result) |
2352 | + |
2353 | + def test_is_bootstrapped_datadir_missing(self): |
2354 | + """FakeJuju.is_bootstrapped() returns False if the data dir is gone.""" |
2355 | + fakejuju = FakeJuju.from_version("1.25.6", "/tmp/fakejuju-no-exist") |
2356 | + result = fakejuju.is_bootstrapped() |
2357 | + |
2358 | + self.assertFalse(result) |
2359 | + |
2360 | + |
2361 | +FAKE_JUJU_SCRIPT = """\ |
2362 | +#!/usr/bin/env python |
2363 | + |
2364 | +import os.path |
2365 | +import sys |
2366 | + |
2367 | +with open("{logfile}", "a") as logfile: |
2368 | + logfile.write(" ".join(sys.argv) + "\\n") |
2369 | + |
2370 | +if sys.argv[1] == "bootstrap": |
2371 | + for filename in ("cert.ca", "fake-juju.log", "fakejuju", "fifo"): |
2372 | + with open(os.path.join("{datadir}", filename), "w"): |
2373 | + pass # Touch the file. |
2374 | + for filename in ("environments",): |
2375 | + with open(os.path.join("{cfgdir}", filename), "w"): |
2376 | + pass # Touch the file. |
2377 | +elif sys.argv[1] in ("api-info", "show-controller"): |
2378 | + print('''{output}''') |
2379 | + |
2380 | +""" |
2381 | + |
2382 | + |
2383 | +def write_fakejuju_script(version, bindir, datadir, cfgdir, api_info): |
2384 | + if version.startswith("1."): |
2385 | + raw_api_info = { |
2386 | + "state-servers": api_info.endpoints, |
2387 | + "user": api_info.user, |
2388 | + "password": api_info.password, |
2389 | + "environ-uuid": api_info.model_uuid, |
2390 | + } |
2391 | + else: |
2392 | + raw_api_info = { |
2393 | + "details": { |
2394 | + "api-endpoints": api_info.endpoints, |
2395 | + }, |
2396 | + "account": { |
2397 | + "user": api_info.user + "@local", |
2398 | + "password": api_info.password, |
2399 | + }, |
2400 | + "models": { |
2401 | + "controller": { |
2402 | + "uuid": api_info.model_uuid, |
2403 | + }, |
2404 | + "default": { |
2405 | + "uuid": api_info.model_uuid, |
2406 | + }, |
2407 | + }, |
2408 | + } |
2409 | + output = json.dumps(raw_api_info) |
2410 | + |
2411 | + logfile = os.path.join(bindir, "calls.log") |
2412 | + script = FAKE_JUJU_SCRIPT.format( |
2413 | + datadir=datadir, cfgdir=cfgdir, logfile=logfile, output=output) |
2414 | + filename = get_filename(version, bindir) |
2415 | + os.makedirs(os.path.dirname(filename)) |
2416 | + with open(filename, "w") as scriptfile: |
2417 | + scriptfile.write(script) |
2418 | + os.chmod(filename, 0o755) |
2419 | + os.makedirs(datadir) |
2420 | + |
2421 | + return logfile |
2422 | + |
2423 | + |
2424 | +@contextmanager |
2425 | +def tempdir(): |
2426 | + cfgdir = tempfile.mkdtemp("fakejuju-test-") |
2427 | + try: |
2428 | + yield cfgdir |
2429 | + finally: |
2430 | + shutil.rmtree(cfgdir) |
2431 | |
2432 | === modified file 'tests/test_fake.py' |
2433 | --- tests/test_fake.py 2016-09-15 20:38:52 +0000 |
2434 | +++ tests/test_fake.py 2016-10-25 17:25:06 +0000 |
2435 | @@ -108,7 +108,7 @@ |
2436 | output = subprocess.check_output(args, env=env) |
2437 | api_info = json.loads(output.decode()) |
2438 | endpoint = str(api_info[name]["details"]["api-endpoints"][0]) |
2439 | - model = api_info[name]["current-model"] |
2440 | + model = api_info[name]["current-model"].split("/", 1)[-1] |
2441 | uuid = api_info[name]["models"][model]["uuid"] |
2442 | password = api_info[name]["account"]["password"] |
2443 | return endpoint, uuid, password |
Command: make ci-test /ci.lscape. net/job/ latch-test- xenial/ 336/
Result: Success
Revno: 63
Branch: lp:~ericsnowcurrently/fake-juju/juju-2.0-support
Jenkins: https:/