Merge lp:~ericsnowcurrently/fake-juju/testing-fixes into lp:~landscape/fake-juju/trunk-old
- testing-fixes
- Merge into trunk-old
Proposed by
Eric Snow
Status: | Superseded |
---|---|
Proposed branch: | lp:~ericsnowcurrently/fake-juju/testing-fixes |
Merge into: | lp:~landscape/fake-juju/trunk-old |
Diff against target: |
1490 lines (+645/-307) 7 files modified
1.25.6/fake-juju.go (+236/-105) 2.0-beta17/fake-juju.go (+233/-136) Makefile (+1/-1) python/Makefile (+1/-1) python/fakejuju/fakejuju.py (+44/-23) python/fakejuju/testing.py (+4/-18) python/fakejuju/tests/test_fakejuju.py (+126/-23) |
To merge this branch: | bzr merge lp:~ericsnowcurrently/fake-juju/testing-fixes |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Landscape | Pending | ||
Landscape | Pending | ||
Review via email: mp+308971@code.launchpad.net |
This proposal has been superseded by a proposal from 2016-10-20.
Commit message
Description of the change
Fix a couple of broken behaviors for testing.
Without these fixes fake-juju can cause problems in tests.
To post a comment you must log in.
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '1.25.6/fake-juju.go' |
2 | --- 1.25.6/fake-juju.go 2016-06-10 17:07:27 +0000 |
3 | +++ 1.25.6/fake-juju.go 2016-10-20 21:22:41 +0000 |
4 | @@ -50,39 +50,36 @@ |
5 | coretesting.MgoTestPackage(t) |
6 | } |
7 | |
8 | -type processInfo struct { |
9 | - Username string |
10 | - WorkDir string |
11 | - EndpointAddr string |
12 | - Uuid string |
13 | - CACert string |
14 | -} |
15 | - |
16 | func handleCommand(command string) error { |
17 | + filenames := newFakeJujuFilenames("", "", "") |
18 | if command == "bootstrap" { |
19 | - return bootstrap() |
20 | + return bootstrap(filenames) |
21 | } |
22 | if command == "api-endpoints" { |
23 | - return apiEndpoints() |
24 | + return apiEndpoints(filenames) |
25 | } |
26 | if command == "api-info" { |
27 | - return apiInfo() |
28 | + return apiInfo(filenames) |
29 | } |
30 | if command == "destroy-environment" { |
31 | - return destroyEnvironment() |
32 | + return destroyEnvironment(filenames) |
33 | } |
34 | return errors.New("command not found") |
35 | } |
36 | |
37 | -func bootstrap() error { |
38 | +func bootstrap(filenames fakejujuFilenames) error { |
39 | + if err := filenames.ensureDirsExist(); err != nil { |
40 | + return err |
41 | + } |
42 | envName, config, err := environmentNameAndConfig() |
43 | if err != nil { |
44 | return err |
45 | } |
46 | + password := config.AdminSecret() |
47 | + |
48 | command := exec.Command(os.Args[0]) |
49 | command.Env = os.Environ() |
50 | - command.Env = append( |
51 | - command.Env, "ADMIN_PASSWORD="+config.AdminSecret()) |
52 | + command.Env = append(command.Env, "ADMIN_PASSWORD="+password) |
53 | defaultSeries, _ := config.DefaultSeries() |
54 | command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries) |
55 | stdout, err := command.StdoutPipe() |
56 | @@ -90,10 +87,23 @@ |
57 | return err |
58 | } |
59 | command.Start() |
60 | - apiInfo, err := parseApiInfo(envName, stdout) |
61 | + |
62 | + result, err := parseApiInfo(stdout) |
63 | if err != nil { |
64 | return err |
65 | } |
66 | + // Get the API info before changing it. The new values might |
67 | + // not work yet. |
68 | + apiInfo := result.apiInfo() |
69 | + // We actually want to report the API user we added in SetUpTest(). |
70 | + result.username = "admin" |
71 | + if password != "" { |
72 | + result.password = password |
73 | + } |
74 | + if err := result.apply(filenames, envName); err != nil { |
75 | + return err |
76 | + } |
77 | + |
78 | dialOpts := api.DialOpts{ |
79 | DialAddressInterval: 50 * time.Millisecond, |
80 | Timeout: 5 * time.Second, |
81 | @@ -123,8 +133,8 @@ |
82 | return errors.New("invalid delta") |
83 | } |
84 | |
85 | -func apiEndpoints() error { |
86 | - info, err := readProcessInfo() |
87 | +func apiEndpoints(filenames fakejujuFilenames) error { |
88 | + info, err := readProcessInfo(filenames) |
89 | if err != nil { |
90 | return err |
91 | } |
92 | @@ -132,23 +142,22 @@ |
93 | return nil |
94 | } |
95 | |
96 | -func apiInfo() error { |
97 | - info, err := readProcessInfo() |
98 | +func apiInfo(filenames fakejujuFilenames) error { |
99 | + info, err := readProcessInfo(filenames) |
100 | if err != nil { |
101 | return err |
102 | } |
103 | - username := strings.Replace(string(info.Username), "dummy-", "", 1) |
104 | - fmt.Printf("{\"user\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", username, info.Uuid, info.EndpointAddr) |
105 | + fmt.Printf("{\"user\": \"%s\", \"password\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", info.Username, info.Password, info.Uuid, info.EndpointAddr) |
106 | return nil |
107 | } |
108 | |
109 | -func destroyEnvironment() error { |
110 | - info, err := readProcessInfo() |
111 | +func destroyEnvironment(filenames fakejujuFilenames) error { |
112 | + info, err := readProcessInfo(filenames) |
113 | if err != nil { |
114 | return err |
115 | } |
116 | - fifoPath := filepath.Join(info.WorkDir, "fifo") |
117 | - fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600) |
118 | + filenames = newFakeJujuFilenames("", "", info.WorkDir) |
119 | + fd, err := os.OpenFile(filenames.fifo(), os.O_APPEND|os.O_WRONLY, 0600) |
120 | if err != nil { |
121 | return err |
122 | } |
123 | @@ -176,93 +185,206 @@ |
124 | return envName, config, nil |
125 | } |
126 | |
127 | -func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) { |
128 | +type processInfo struct { |
129 | + Username string |
130 | + Password string |
131 | + WorkDir string |
132 | + EndpointAddr string |
133 | + Uuid string |
134 | + CACert []byte |
135 | +} |
136 | + |
137 | +func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) { |
138 | + infoPath := filenames.info() |
139 | + data, err := ioutil.ReadFile(infoPath) |
140 | + if err != nil { |
141 | + return nil, err |
142 | + } |
143 | + info := &processInfo{} |
144 | + err = goyaml.Unmarshal(data, info) |
145 | + if err != nil { |
146 | + return nil, err |
147 | + } |
148 | + return info, nil |
149 | +} |
150 | + |
151 | +func (info processInfo) write(infoPath string) error { |
152 | + data, _ := goyaml.Marshal(&info) |
153 | + if err := ioutil.WriteFile(infoPath, data, 0644); err != nil { |
154 | + return err |
155 | + } |
156 | + return nil |
157 | +} |
158 | + |
159 | +type fakejujuFilenames struct { |
160 | + datadir string |
161 | + logsdir string |
162 | +} |
163 | + |
164 | +func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames { |
165 | + if datadir == "" { |
166 | + datadir = os.Getenv("FAKE_JUJU_DATA_DIR") |
167 | + if datadir == "" { |
168 | + if jujucfgdir == "" { |
169 | + jujucfgdir = os.Getenv("JUJU_HOME") |
170 | + } |
171 | + datadir = jujucfgdir |
172 | + } |
173 | + } |
174 | + if logsdir == "" { |
175 | + logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR") |
176 | + if logsdir == "" { |
177 | + logsdir = datadir |
178 | + } |
179 | + } |
180 | + return fakejujuFilenames{datadir, logsdir} |
181 | +} |
182 | + |
183 | +func (fj fakejujuFilenames) ensureDirsExist() error { |
184 | + if err := os.MkdirAll(fj.datadir, 0755); err != nil { |
185 | + return err |
186 | + } |
187 | + if err := os.MkdirAll(fj.logsdir, 0755); err != nil { |
188 | + return err |
189 | + } |
190 | + return nil |
191 | +} |
192 | + |
193 | +func (fj fakejujuFilenames) info() string { |
194 | + return filepath.Join(fj.datadir, "fakejuju") |
195 | +} |
196 | + |
197 | +func (fj fakejujuFilenames) logs() string { |
198 | + return filepath.Join(fj.logsdir, "fake-juju.log") |
199 | +} |
200 | + |
201 | +func (fj fakejujuFilenames) fifo() string { |
202 | + return filepath.Join(fj.datadir, "fifo") |
203 | +} |
204 | + |
205 | +func (fj fakejujuFilenames) cacert() string { |
206 | + return filepath.Join(fj.datadir, "cert.ca") |
207 | +} |
208 | + |
209 | +type bootstrapResult struct { |
210 | + dummyEnvName string |
211 | + cfgdir string |
212 | + uuid string |
213 | + username string |
214 | + password string |
215 | + addresses []string |
216 | + caCert []byte |
217 | +} |
218 | + |
219 | +func (br bootstrapResult) apiInfo() *api.Info { |
220 | + return &api.Info{ |
221 | + Addrs: br.addresses, |
222 | + Tag: names.NewLocalUserTag(br.username), |
223 | + Password: br.password, |
224 | + CACert: string(br.caCert), |
225 | + EnvironTag: names.NewEnvironTag(br.uuid), |
226 | + } |
227 | +} |
228 | + |
229 | +func (br bootstrapResult) fakeJujuInfo() *processInfo { |
230 | + return &processInfo{ |
231 | + Username: br.username, |
232 | + Password: br.password, |
233 | + WorkDir: br.cfgdir, |
234 | + EndpointAddr: br.addresses[0], |
235 | + Uuid: br.uuid, |
236 | + CACert: br.caCert, |
237 | + } |
238 | +} |
239 | + |
240 | +func (br bootstrapResult) logsSymlink(target string) (string, string) { |
241 | + if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" || os.Getenv("FAKE_JUJU_DATA_DIR") != "" { |
242 | + return "", "" |
243 | + } |
244 | + |
245 | + filenames := newFakeJujuFilenames("", "", br.cfgdir) |
246 | + source := filenames.logs() |
247 | + return source, target |
248 | +} |
249 | + |
250 | +func (br bootstrapResult) jenvSymlink(jujuHome, envName string) (string, string) { |
251 | + if jujuHome == "" || envName == "" { |
252 | + return "", "" |
253 | + } |
254 | + |
255 | + source := filepath.Join(br.cfgdir, "environments", br.dummyEnvName+".jenv") |
256 | + target := filepath.Join(jujuHome, "environments", envName+".jenv") |
257 | + return source, target |
258 | +} |
259 | + |
260 | +func (br bootstrapResult) apply(filenames fakejujuFilenames, envName string) error { |
261 | + if err := br.fakeJujuInfo().write(filenames.info()); err != nil { |
262 | + return err |
263 | + } |
264 | + |
265 | + logsSource, logsTarget := br.logsSymlink(filenames.logs()) |
266 | + if logsSource != "" && logsTarget != "" { |
267 | + if err := os.Symlink(logsSource, logsTarget); err != nil { |
268 | + return err |
269 | + } |
270 | + } |
271 | + |
272 | + jenvSource, jenvTarget := br.jenvSymlink(os.Getenv("JUJU_HOME"), envName) |
273 | + if jenvSource != "" && jenvTarget != "" { |
274 | + if err := os.MkdirAll(filepath.Dir(jenvTarget), 0755); err != nil { |
275 | + return err |
276 | + } |
277 | + if err := os.Symlink(jenvSource, jenvTarget); err != nil { |
278 | + return err |
279 | + } |
280 | + } |
281 | + |
282 | + if err := ioutil.WriteFile(filenames.cacert(), br.caCert, 0644); err != nil { |
283 | + return err |
284 | + } |
285 | + |
286 | + return nil |
287 | +} |
288 | + |
289 | +// See github.com/juju/juju/blob/juju/testing/conn.go. |
290 | +const dummyEnvName = "dummyenv" |
291 | + |
292 | +func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) { |
293 | buffer := bufio.NewReader(stdout) |
294 | + |
295 | line, _, err := buffer.ReadLine() |
296 | if err != nil { |
297 | return nil, err |
298 | } |
299 | uuid := string(line) |
300 | - environTag := names.NewEnvironTag(uuid) |
301 | + |
302 | line, _, err = buffer.ReadLine() |
303 | if err != nil { |
304 | return nil, err |
305 | } |
306 | workDir := string(line) |
307 | + |
308 | store, err := configstore.NewDisk(workDir) |
309 | if err != nil { |
310 | return nil, err |
311 | } |
312 | - info, err := store.ReadInfo("dummyenv") |
313 | + info, err := store.ReadInfo(dummyEnvName) |
314 | if err != nil { |
315 | return nil, err |
316 | } |
317 | + |
318 | credentials := info.APICredentials() |
319 | endpoint := info.APIEndpoint() |
320 | - addresses := endpoint.Addresses |
321 | - apiInfo := &api.Info{ |
322 | - Addrs: addresses, |
323 | - Tag: names.NewLocalUserTag(credentials.User), |
324 | - Password: credentials.Password, |
325 | - CACert: endpoint.CACert, |
326 | - EnvironTag: environTag, |
327 | - } |
328 | - err = writeProcessInfo(envName, &processInfo{ |
329 | - Username: credentials.User, |
330 | - WorkDir: workDir, |
331 | - EndpointAddr: addresses[0], |
332 | - Uuid: uuid, |
333 | - CACert: endpoint.CACert, |
334 | - }) |
335 | - if err != nil { |
336 | - return nil, err |
337 | - } |
338 | - return apiInfo, nil |
339 | -} |
340 | - |
341 | -func readProcessInfo() (*processInfo, error) { |
342 | - infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju") |
343 | - data, err := ioutil.ReadFile(infoPath) |
344 | - if err != nil { |
345 | - return nil, err |
346 | - } |
347 | - info := &processInfo{} |
348 | - err = goyaml.Unmarshal(data, info) |
349 | - if err != nil { |
350 | - return nil, err |
351 | - } |
352 | - return info, nil |
353 | -} |
354 | - |
355 | -func writeProcessInfo(envName string, info *processInfo) error { |
356 | - var err error |
357 | - jujuHome := os.Getenv("JUJU_HOME") |
358 | - infoPath := filepath.Join(jujuHome, "fakejuju") |
359 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
360 | - if logsDir == "" { |
361 | - logsDir = jujuHome |
362 | - } |
363 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
364 | - caCertPath := filepath.Join(jujuHome, "cert.ca") |
365 | - envPath := filepath.Join(jujuHome, "environments") |
366 | - os.Mkdir(envPath, 0755) |
367 | - jEnvPath := filepath.Join(envPath, envName+".jenv") |
368 | - data, _ := goyaml.Marshal(info) |
369 | - if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" { |
370 | - err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath) |
371 | - if err != nil { |
372 | - return err |
373 | - } |
374 | - } |
375 | - err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath) |
376 | - if err != nil { |
377 | - return err |
378 | - } |
379 | - err = ioutil.WriteFile(infoPath, data, 0644) |
380 | - if err != nil { |
381 | - return err |
382 | - } |
383 | - return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644) |
384 | + result := &bootstrapResult{ |
385 | + dummyEnvName: dummyEnvName, |
386 | + cfgdir: workDir, |
387 | + uuid: uuid, |
388 | + username: credentials.User, |
389 | + password: credentials.Password, |
390 | + addresses: endpoint.Addresses, |
391 | + caCert: []byte(endpoint.CACert), |
392 | + } |
393 | + return result, nil |
394 | } |
395 | |
396 | // Read the failures info file pointed by the FAKE_JUJU_FAILURES environment |
397 | @@ -307,7 +429,7 @@ |
398 | |
399 | instanceCount int |
400 | machineStarted map[string]bool |
401 | - fifoPath string |
402 | + filenames fakejujuFilenames |
403 | logFile *os.File |
404 | } |
405 | |
406 | @@ -359,7 +481,6 @@ |
407 | c.Assert(err, gc.IsNil) |
408 | |
409 | apiInfo := s.APIInfo(c) |
410 | - //fmt.Println(apiInfo.Addrs[0]) |
411 | jujuHome := osenv.JujuHome() |
412 | // IMPORTANT: don't remove this logging because it's used by the |
413 | // bootstrap command. |
414 | @@ -374,15 +495,11 @@ |
415 | c.Assert(err, gc.IsNil) |
416 | os.Setenv("PATH", binPath+":"+os.Getenv("PATH")) |
417 | |
418 | - s.fifoPath = filepath.Join(jujuHome, "fifo") |
419 | - syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0) |
420 | + s.filenames = newFakeJujuFilenames("", "", jujuHome) |
421 | + syscall.Mknod(s.filenames.fifo(), syscall.S_IFIFO|0666, 0) |
422 | |
423 | // Logging |
424 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
425 | - if logsDir == "" { |
426 | - logsDir = jujuHome |
427 | - } |
428 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
429 | + logPath := s.filenames.logs() |
430 | s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
431 | c.Assert(err, gc.IsNil) |
432 | |
433 | @@ -402,16 +519,30 @@ |
434 | } |
435 | |
436 | func (s *FakeJujuSuite) TestStart(c *gc.C) { |
437 | + fifoPath := s.filenames.fifo() |
438 | watcher := s.State.Watch() |
439 | go func() { |
440 | - log.Println("Open commands FIFO", s.fifoPath) |
441 | - fd, err := os.Open(s.fifoPath) |
442 | + log.Println("Open commands FIFO", fifoPath) |
443 | + fd, err := os.Open(fifoPath) |
444 | if err != nil { |
445 | log.Println("Failed to open commands FIFO") |
446 | } |
447 | c.Assert(err, gc.IsNil) |
448 | + defer func() { |
449 | + if err := fd.Close(); err != nil { |
450 | + c.Logf("failed closing FIFO file: %s", err) |
451 | + } |
452 | + // Mark the controller as destroyed by renaming some files. |
453 | + if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil { |
454 | + c.Logf("failed renaming FIFO file: %s", err) |
455 | + } |
456 | + infofile := s.filenames.info() |
457 | + if err := os.Rename(infofile, infofile+".destroyed"); err != nil { |
458 | + c.Logf("failed renaming info file: %s", err) |
459 | + } |
460 | + }() |
461 | scanner := bufio.NewScanner(fd) |
462 | - log.Println("Listen for commands on FIFO", s.fifoPath) |
463 | + log.Println("Listen for commands on FIFO", fifoPath) |
464 | scanner.Scan() |
465 | log.Println("Stopping fake-juju") |
466 | watcher.Stop() |
467 | |
468 | === modified file '2.0-beta17/fake-juju.go' |
469 | --- 2.0-beta17/fake-juju.go 2016-09-15 19:05:50 +0000 |
470 | +++ 2.0-beta17/fake-juju.go 2016-10-20 21:22:41 +0000 |
471 | @@ -51,33 +51,31 @@ |
472 | coretesting.MgoTestPackage(t) |
473 | } |
474 | |
475 | -type processInfo struct { |
476 | - WorkDir string |
477 | - EndpointAddr string |
478 | - Uuid string |
479 | - CACert string |
480 | -} |
481 | - |
482 | func handleCommand(command string) error { |
483 | + filenames := newFakeJujuFilenames("", "", "") |
484 | if command == "bootstrap" { |
485 | - return bootstrap() |
486 | + return bootstrap(filenames) |
487 | } |
488 | if command == "show-controller" { |
489 | - return apiInfo() |
490 | + return apiInfo(filenames) |
491 | } |
492 | if command == "destroy-controller" { |
493 | - return destroyEnvironment() |
494 | + return destroyController(filenames) |
495 | } |
496 | return errors.New("command not found") |
497 | } |
498 | |
499 | -func bootstrap() error { |
500 | +func bootstrap(filenames fakejujuFilenames) error { |
501 | argc := len(os.Args) |
502 | if argc < 4 { |
503 | return errors.New( |
504 | "error: controller name and cloud name are required") |
505 | } |
506 | - envName := os.Args[argc-2] |
507 | + if err := filenames.ensureDirsExist(); err != nil { |
508 | + return err |
509 | + } |
510 | + // XXX Swap the 2 args for juju-2.0-final. |
511 | + controllerName := os.Args[argc-2] |
512 | command := exec.Command(os.Args[0]) |
513 | command.Env = os.Environ() |
514 | command.Env = append( |
515 | @@ -89,10 +87,16 @@ |
516 | return err |
517 | } |
518 | command.Start() |
519 | - apiInfo, err := parseApiInfo(envName, stdout) |
520 | + |
521 | + result, err := parseApiInfo(stdout) |
522 | if err != nil { |
523 | return err |
524 | } |
525 | + if err := result.apply(filenames, controllerName); err != nil { |
526 | + return err |
527 | + } |
528 | + apiInfo := result.apiInfo() |
529 | + |
530 | dialOpts := api.DialOpts{ |
531 | DialAddressInterval: 50 * time.Millisecond, |
532 | Timeout: 5 * time.Second, |
533 | @@ -122,8 +126,8 @@ |
534 | return errors.New("invalid delta") |
535 | } |
536 | |
537 | -func apiInfo() error { |
538 | - info, err := readProcessInfo() |
539 | +func apiInfo(filenames fakejujuFilenames) error { |
540 | + info, err := readProcessInfo(filenames) |
541 | if err != nil { |
542 | return err |
543 | } |
544 | @@ -140,13 +144,13 @@ |
545 | return nil |
546 | } |
547 | |
548 | -func destroyEnvironment() error { |
549 | - info, err := readProcessInfo() |
550 | +func destroyController(filenames fakejujuFilenames) error { |
551 | + info, err := readProcessInfo(filenames) |
552 | if err != nil { |
553 | return err |
554 | } |
555 | - fifoPath := filepath.Join(info.WorkDir, "fifo") |
556 | - fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600) |
557 | + filenames = newFakeJujuFilenames("", "", info.WorkDir) |
558 | + fd, err := os.OpenFile(filenames.fifo(), os.O_APPEND|os.O_WRONLY, 0600) |
559 | if err != nil { |
560 | return err |
561 | } |
562 | @@ -158,13 +162,187 @@ |
563 | return nil |
564 | } |
565 | |
566 | -func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) { |
567 | +type processInfo struct { |
568 | + WorkDir string |
569 | + EndpointAddr string |
570 | + Uuid string |
571 | + CACert []byte |
572 | +} |
573 | + |
574 | +func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) { |
575 | + infoPath := filenames.info() |
576 | + data, err := ioutil.ReadFile(infoPath) |
577 | + if err != nil { |
578 | + return nil, err |
579 | + } |
580 | + info := &processInfo{} |
581 | + err = goyaml.Unmarshal(data, info) |
582 | + if err != nil { |
583 | + return nil, err |
584 | + } |
585 | + return info, nil |
586 | +} |
587 | + |
588 | +func (info processInfo) write(infoPath string) error { |
589 | + data, _ := goyaml.Marshal(&info) |
590 | + if err := ioutil.WriteFile(infoPath, data, 0644); err != nil { |
591 | + return err |
592 | + } |
593 | + return nil |
594 | +} |
595 | + |
596 | +type fakejujuFilenames struct { |
597 | + datadir string |
598 | + logsdir string |
599 | +} |
600 | + |
601 | +func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames { |
602 | + if datadir == "" { |
603 | + datadir = os.Getenv("FAKE_JUJU_DATA_DIR") |
604 | + if datadir == "" { |
605 | + if jujucfgdir == "" { |
606 | + jujucfgdir = os.Getenv("JUJU_DATA") |
607 | + } |
608 | + datadir = jujucfgdir |
609 | + } |
610 | + } |
611 | + if logsdir == "" { |
612 | + logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR") |
613 | + if logsdir == "" { |
614 | + logsdir = datadir |
615 | + } |
616 | + } |
617 | + return fakejujuFilenames{datadir, logsdir} |
618 | +} |
619 | + |
620 | +func (fj fakejujuFilenames) ensureDirsExist() error { |
621 | + if err := os.MkdirAll(fj.datadir, 0755); err != nil { |
622 | + return err |
623 | + } |
624 | + if err := os.MkdirAll(fj.logsdir, 0755); err != nil { |
625 | + return err |
626 | + } |
627 | + return nil |
628 | +} |
629 | + |
630 | +func (fj fakejujuFilenames) info() string { |
631 | + return filepath.Join(fj.datadir, "fakejuju") |
632 | +} |
633 | + |
634 | +func (fj fakejujuFilenames) logs() string { |
635 | + return filepath.Join(fj.logsdir, "fake-juju.log") |
636 | +} |
637 | + |
638 | +func (fj fakejujuFilenames) fifo() string { |
639 | + return filepath.Join(fj.datadir, "fifo") |
640 | +} |
641 | + |
642 | +func (fj fakejujuFilenames) cacert() string { |
643 | + return filepath.Join(fj.datadir, "cert.ca") |
644 | +} |
645 | + |
646 | +type bootstrapResult struct { |
647 | + dummyControllerName string |
648 | + cfgdir string |
649 | + uuid string |
650 | + username string |
651 | + password string |
652 | + addresses []string |
653 | + caCert []byte |
654 | +} |
655 | + |
656 | +func (br bootstrapResult) apiInfo() *api.Info { |
657 | + return &api.Info{ |
658 | + Addrs: br.addresses, |
659 | + Tag: names.NewUserTag(br.username), |
660 | + Password: br.password, |
661 | + CACert: string(br.caCert), |
662 | + ModelTag: names.NewModelTag(br.uuid), |
663 | + } |
664 | +} |
665 | + |
666 | +func (br bootstrapResult) fakeJujuInfo() *processInfo { |
667 | + return &processInfo{ |
668 | + WorkDir: br.cfgdir, |
669 | + EndpointAddr: br.addresses[0], |
670 | + Uuid: br.uuid, |
671 | + CACert: br.caCert, |
672 | + } |
673 | +} |
674 | + |
675 | +func (br bootstrapResult) logsSymlink(target string) (string, string) { |
676 | + if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" { |
677 | + return "", "" |
678 | + } |
679 | + |
680 | + filenames := newFakeJujuFilenames("", "", br.cfgdir) |
681 | + source := filenames.logs() |
682 | + return source, target |
683 | +} |
684 | + |
685 | +func (br bootstrapResult) apply(filenames fakejujuFilenames, controllerName string) error { |
686 | + if err := br.fakeJujuInfo().write(filenames.info()); err != nil { |
687 | + return err |
688 | + } |
689 | + |
690 | + logsSource, logsTarget := br.logsSymlink(filenames.logs()) |
691 | + if logsSource != "" && logsTarget != "" { |
692 | + if err := os.Symlink(logsSource, logsTarget); err != nil { |
693 | + return err |
694 | + } |
695 | + } |
696 | + |
697 | + if err := br.copyConfig(os.Getenv("JUJU_DATA"), controllerName); err != nil { |
698 | + return err |
699 | + } |
700 | + |
701 | + if err := ioutil.WriteFile(filenames.cacert(), br.caCert, 0644); err != nil { |
702 | + return err |
703 | + } |
704 | + |
705 | + return nil |
706 | +} |
707 | + |
708 | +func (br bootstrapResult) copyConfig(cfgdir, controllerName string) error { |
709 | + for _, name := range []string{"controllers.yaml", "models.yaml", "accounts.yaml"} { |
710 | + source := filepath.Join(br.cfgdir, name) |
711 | + target := filepath.Join(cfgdir, name) |
712 | + |
713 | + input, err := ioutil.ReadFile(source) |
714 | + if err != nil { |
715 | + return err |
716 | + } |
717 | + // Generated configuration by test fixtures has the controller name |
718 | + // hard-coded to "kontroll". A simple replace should fix this for |
719 | + // clients using this config and expecting a specific controller |
720 | + // name. |
721 | + output := strings.Replace(string(input), dummyControllerName, controllerName, -1) |
722 | + err = ioutil.WriteFile(target, []byte(output), 0644) |
723 | + if err != nil { |
724 | + return err |
725 | + } |
726 | + } |
727 | + |
728 | + current := filepath.Join(cfgdir, "current-controller") |
729 | + if err := ioutil.WriteFile(current, []byte(controllerName), 0644); err != nil { |
730 | + return err |
731 | + } |
732 | + |
733 | + return nil |
734 | +} |
735 | + |
736 | +// See github.com/juju/juju/blob/juju/testing/conn.go. |
737 | +const dummyControllerName = "kontroll" |
738 | + |
739 | +func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) { |
740 | buffer := bufio.NewReader(stdout) |
741 | + |
742 | line, _, err := buffer.ReadLine() |
743 | if err != nil { |
744 | return nil, err |
745 | } |
746 | uuid := string(line) |
747 | + |
748 | line, _, err = buffer.ReadLine() |
749 | if err != nil { |
750 | return nil, err |
751 | @@ -175,8 +353,8 @@ |
752 | store := jujuclient.NewFileClientStore() |
753 | // hard-coded value in juju testing |
754 | // This will be replaced in JUJU_DATA copy of the juju client config. |
755 | - currentController := "kontroll" |
756 | - one, err := store.ControllerByName("kontroll") |
757 | + currentController := dummyControllerName |
758 | + one, err := store.ControllerByName(currentController) |
759 | if err != nil { |
760 | return nil, err |
761 | } |
762 | @@ -185,108 +363,17 @@ |
763 | if err != nil { |
764 | return nil, err |
765 | } |
766 | - apiInfo := &api.Info{ |
767 | - Addrs: one.APIEndpoints, |
768 | - Tag: names.NewUserTag(accountDetails.User), |
769 | - Password: accountDetails.Password, |
770 | - CACert: one.CACert, |
771 | - ModelTag: names.NewModelTag(uuid), |
772 | - } |
773 | - |
774 | - err = writeProcessInfo(envName, &processInfo{ |
775 | - WorkDir: workDir, |
776 | - EndpointAddr: one.APIEndpoints[0], |
777 | - Uuid: uuid, |
778 | - CACert: one.CACert, |
779 | - }) |
780 | - if err != nil { |
781 | - return nil, err |
782 | - } |
783 | - return apiInfo, nil |
784 | -} |
785 | - |
786 | -func readProcessInfo() (*processInfo, error) { |
787 | - infoPath := filepath.Join(os.Getenv("JUJU_DATA"), "fakejuju") |
788 | - data, err := ioutil.ReadFile(infoPath) |
789 | - if err != nil { |
790 | - return nil, err |
791 | - } |
792 | - info := &processInfo{} |
793 | - err = goyaml.Unmarshal(data, info) |
794 | - if err != nil { |
795 | - return nil, err |
796 | - } |
797 | - return info, nil |
798 | -} |
799 | - |
800 | -func writeProcessInfo(envName string, info *processInfo) error { |
801 | - var err error |
802 | - jujuHome := os.Getenv("JUJU_DATA") |
803 | - infoPath := filepath.Join(jujuHome, "fakejuju") |
804 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
805 | - if logsDir == "" { |
806 | - logsDir = jujuHome |
807 | - } |
808 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
809 | - caCertPath := filepath.Join(jujuHome, "cert.ca") |
810 | - data, _ := goyaml.Marshal(info) |
811 | - if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" { |
812 | - err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath) |
813 | - if err != nil { |
814 | - return err |
815 | - } |
816 | - } |
817 | - |
818 | - err = copyClientConfig( |
819 | - filepath.Join(info.WorkDir, "controllers.yaml"), |
820 | - filepath.Join(jujuHome, "controllers.yaml"), |
821 | - envName) |
822 | - if err != nil { |
823 | - return err |
824 | - } |
825 | - err = copyClientConfig( |
826 | - filepath.Join(info.WorkDir, "models.yaml"), |
827 | - filepath.Join(jujuHome, "models.yaml"), |
828 | - envName) |
829 | - if err != nil { |
830 | - return err |
831 | - } |
832 | - err = copyClientConfig( |
833 | - filepath.Join(info.WorkDir, "accounts.yaml"), |
834 | - filepath.Join(jujuHome, "accounts.yaml"), |
835 | - envName) |
836 | - if err != nil { |
837 | - return err |
838 | - } |
839 | - err = ioutil.WriteFile( |
840 | - filepath.Join(jujuHome, "current-controller"), |
841 | - []byte(envName), 0644) |
842 | - if err != nil { |
843 | - return err |
844 | - } |
845 | - |
846 | - err = ioutil.WriteFile(infoPath, data, 0644) |
847 | - if err != nil { |
848 | - return err |
849 | - } |
850 | - return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644) |
851 | -} |
852 | - |
853 | -func copyClientConfig(src string, dst string, envName string) error { |
854 | - input, err := ioutil.ReadFile(src) |
855 | - if err != nil { |
856 | - return err |
857 | - } |
858 | - // Generated configuration by test fixtures has the controller name |
859 | - // hard-coded to "kontroll". A simple replace should fix this for |
860 | - // clients using this config and expecting a specific controller |
861 | - // name. |
862 | - output := strings.Replace(string(input), "kontroll", envName, -1) |
863 | - err = ioutil.WriteFile(dst, []byte(output), 0644) |
864 | - if err != nil { |
865 | - return err |
866 | - } |
867 | - return nil |
868 | + |
869 | + result := &bootstrapResult{ |
870 | + dummyControllerName: dummyControllerName, |
871 | + cfgdir: workDir, |
872 | + uuid: uuid, |
873 | + username: accountDetails.User, |
874 | + password: accountDetails.Password, |
875 | + addresses: one.APIEndpoints, |
876 | + caCert: []byte(one.CACert), |
877 | + } |
878 | + return result, nil |
879 | } |
880 | |
881 | // Read the failures info file pointed by the FAKE_JUJU_FAILURES environment |
882 | @@ -331,7 +418,7 @@ |
883 | |
884 | instanceCount int |
885 | machineStarted map[string]bool |
886 | - fifoPath string |
887 | + filenames fakejujuFilenames |
888 | logFile *os.File |
889 | } |
890 | |
891 | @@ -398,20 +485,16 @@ |
892 | c.Assert(err, gc.IsNil) |
893 | os.Setenv("PATH", binPath+":"+os.Getenv("PATH")) |
894 | |
895 | - s.fifoPath = filepath.Join(jujuHome, "fifo") |
896 | - syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0) |
897 | + s.filenames = newFakeJujuFilenames("", "", jujuHome) |
898 | + syscall.Mknod(s.filenames.fifo(), syscall.S_IFIFO|0666, 0) |
899 | |
900 | // Logging |
901 | - logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR") |
902 | - if logsDir == "" { |
903 | - logsDir = jujuHome |
904 | - } |
905 | - logPath := filepath.Join(logsDir, "fake-juju.log") |
906 | + logPath := s.filenames.logs() |
907 | s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
908 | c.Assert(err, gc.IsNil) |
909 | |
910 | log.SetOutput(s.logFile) |
911 | - log.Println("Started fake-juju at", jujuHome) |
912 | + log.Println("Started fake-juju at ", jujuHome) |
913 | |
914 | } |
915 | |
916 | @@ -423,16 +506,30 @@ |
917 | } |
918 | |
919 | func (s *FakeJujuSuite) TestStart(c *gc.C) { |
920 | + fifoPath := s.filenames.fifo() |
921 | watcher := s.State.Watch() |
922 | go func() { |
923 | - log.Println("Open commands FIFO", s.fifoPath) |
924 | - fd, err := os.Open(s.fifoPath) |
925 | + log.Println("Open commands FIFO", fifoPath) |
926 | + fd, err := os.Open(fifoPath) |
927 | if err != nil { |
928 | log.Println("Failed to open commands FIFO") |
929 | } |
930 | c.Assert(err, gc.IsNil) |
931 | + defer func() { |
932 | + if err := fd.Close(); err != nil { |
933 | + c.Logf("failed closing FIFO file: %s", err) |
934 | + } |
935 | + // Mark the controller as destroyed by renaming some files. |
936 | + if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil { |
937 | + c.Logf("failed renaming FIFO file: %s", err) |
938 | + } |
939 | + infofile := s.filenames.info() |
940 | + if err := os.Rename(infofile, infofile+".destroyed"); err != nil { |
941 | + c.Logf("failed renaming info file: %s", err) |
942 | + } |
943 | + }() |
944 | scanner := bufio.NewScanner(fd) |
945 | - log.Println("Listen for commands on FIFO", s.fifoPath) |
946 | + log.Println("Listen for commands on FIFO", fifoPath) |
947 | scanner.Scan() |
948 | log.Println("Stopping fake-juju") |
949 | watcher.Stop() |
950 | |
951 | === modified file 'Makefile' |
952 | --- Makefile 2016-09-20 18:26:47 +0000 |
953 | +++ Makefile 2016-10-20 21:22:41 +0000 |
954 | @@ -11,7 +11,7 @@ |
955 | INSTALLDIR = $(DESTDIR)/usr/bin |
956 | INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION) |
957 | |
958 | -$(JUJU_VERSION)/$(JUJU_VERSION): |
959 | +$(JUJU_VERSION)/$(JUJU_VERSION): $(JUJU_VERSION)/fake-juju.go |
960 | case $(JUJU_VERSION) in \ |
961 | 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\ |
962 | 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\ |
963 | |
964 | === modified file 'python/Makefile' |
965 | --- python/Makefile 2016-10-06 21:44:31 +0000 |
966 | +++ python/Makefile 2016-10-20 21:22:41 +0000 |
967 | @@ -6,4 +6,4 @@ |
968 | |
969 | .PHONY: install-dev |
970 | install-dev: |
971 | - ln -s $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju |
972 | + ln -snv $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju |
973 | |
974 | === modified file 'python/fakejuju/fakejuju.py' |
975 | --- python/fakejuju/fakejuju.py 2016-10-17 15:54:59 +0000 |
976 | +++ python/fakejuju/fakejuju.py 2016-10-20 21:22:41 +0000 |
977 | @@ -2,6 +2,7 @@ |
978 | |
979 | import os.path |
980 | |
981 | +import txjuju |
982 | import txjuju.cli |
983 | |
984 | from .failures import Failures |
985 | @@ -23,15 +24,17 @@ |
986 | return os.path.join(bindir, filename) |
987 | |
988 | |
989 | -def set_envvars(envvars, failures_filename=None, logsdir=None): |
990 | +def set_envvars(envvars, datadir=None, failures_filename=None, logsdir=None): |
991 | """Return the environment variables with which to run fake-juju. |
992 | |
993 | @param envvars: The env dict to update. |
994 | + @param datadir: The fake-juju data directory. |
995 | @param failures_filename: The path to the failures file that |
996 | fake-juju will use. |
997 | @params logsdir: The path to the directory where fake-juju will |
998 | write its log files. |
999 | """ |
1000 | + envvars["FAKE_JUJU_DATA_DIR"] = datadir or "" |
1001 | envvars["FAKE_JUJU_FAILURES"] = failures_filename or "" |
1002 | envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or "" |
1003 | |
1004 | @@ -40,46 +43,47 @@ |
1005 | """The fundamental details for fake-juju.""" |
1006 | |
1007 | @classmethod |
1008 | - def from_version(cls, version, cfgdir, |
1009 | + def from_version(cls, version, datadir, |
1010 | logsdir=None, failuresdir=None, bindir=None): |
1011 | """Return a new instance given the provided information. |
1012 | |
1013 | @param version: The Juju version to fake. |
1014 | - @param cfgdir: The "juju home" directory to use. |
1015 | + @param datadir: The directory in which to store files specific |
1016 | + to fake-juju. |
1017 | @param logsdir: The directory where logs will be written. |
1018 | - This defaults to cfgdir. |
1019 | + This defaults to datadir. |
1020 | @params failuresdir: The directory where failure injection |
1021 | is managed. |
1022 | @param bindir: The directory containing the fake-juju binary. |
1023 | This defaults to /usr/bin. |
1024 | """ |
1025 | - if logsdir is None: |
1026 | - logsdir = cfgdir |
1027 | if failuresdir is None: |
1028 | - failuresdir = cfgdir |
1029 | + failuresdir = datadir |
1030 | filename = get_filename(version, bindir=bindir) |
1031 | failures = Failures(failuresdir) |
1032 | - return cls(filename, version, cfgdir, logsdir, failures) |
1033 | + return cls(filename, version, datadir, logsdir, failures) |
1034 | |
1035 | - def __init__(self, filename, version, cfgdir, logsdir=None, failures=None): |
1036 | + def __init__(self, filename, version, datadir, |
1037 | + logsdir=None, failures=None): |
1038 | """ |
1039 | @param filename: The path to the fake-juju binary. |
1040 | @param version: The Juju version to fake. |
1041 | - @param cfgdir: The "juju home" directory to use. |
1042 | + @param datadir: The directory in which to store files specific |
1043 | + to fake-juju. |
1044 | @param logsdir: The directory where logs will be written. |
1045 | - This defaults to cfgdir. |
1046 | + This defaults to datadir. |
1047 | @param failures: The set of fake-juju failures to use. |
1048 | """ |
1049 | - logsdir = logsdir if logsdir is not None else cfgdir |
1050 | - if failures is None and cfgdir: |
1051 | - failures = Failures(cfgdir) |
1052 | + logsdir = logsdir if logsdir is not None else datadir |
1053 | + if failures is None and datadir: |
1054 | + failures = Failures(datadir) |
1055 | |
1056 | if not filename: |
1057 | raise ValueError("missing filename") |
1058 | if not version: |
1059 | raise ValueError("missing version") |
1060 | - if not cfgdir: |
1061 | - raise ValueError("missing cfgdir") |
1062 | + if not datadir: |
1063 | + raise ValueError("missing datadir") |
1064 | if not logsdir: |
1065 | raise ValueError("missing logsdir") |
1066 | if failures is None: |
1067 | @@ -87,7 +91,7 @@ |
1068 | |
1069 | self.filename = filename |
1070 | self.version = version |
1071 | - self.cfgdir = cfgdir |
1072 | + self.datadir = datadir |
1073 | self.logsdir = logsdir |
1074 | self.failures = failures |
1075 | |
1076 | @@ -99,19 +103,19 @@ |
1077 | @property |
1078 | def infofile(self): |
1079 | """The path to fake-juju's data cache.""" |
1080 | - return os.path.join(self.cfgdir, "fakejuju") |
1081 | + return os.path.join(self.datadir, "fakejuju") |
1082 | |
1083 | @property |
1084 | def fifo(self): |
1085 | """The path to the fifo file that triggers shutdown.""" |
1086 | - return os.path.join(self.cfgdir, "fifo") |
1087 | + return os.path.join(self.datadir, "fifo") |
1088 | |
1089 | @property |
1090 | def cacertfile(self): |
1091 | """The path to the API server's certificate.""" |
1092 | - return os.path.join(self.cfgdir, "cert.ca") |
1093 | + return os.path.join(self.datadir, "cert.ca") |
1094 | |
1095 | - def cli(self, envvars=None): |
1096 | + def cli(self, cfgdir, envvars=None): |
1097 | """Return the txjuju.cli.CLI for this fake-juju. |
1098 | |
1099 | Currently fake-juju supports only the following juju subcommands: |
1100 | @@ -123,10 +127,27 @@ |
1101 | Note that passwords are always omited, even if requested. |
1102 | * api-endpoints |
1103 | * destroy-environment |
1104 | + |
1105 | + Note that fake-juju ignores local config files. |
1106 | """ |
1107 | if envvars is None: |
1108 | envvars = os.environ |
1109 | envvars = dict(envvars) |
1110 | - set_envvars(envvars, self.failures._filename, self.logsdir) |
1111 | + set_envvars( |
1112 | + envvars, self.datadir, self.failures._filename, self.logsdir) |
1113 | return txjuju.cli.CLI.from_version( |
1114 | - self.filename, self.version, self.cfgdir, envvars) |
1115 | + self.filename, self.version, cfgdir, envvars) |
1116 | + |
1117 | + def bootstrap(self, name, cfgdir, admin_secret=None): |
1118 | + """Return the CLI and APIInfo after bootstrapping from scratch.""" |
1119 | + from . import get_bootstrap_spec |
1120 | + spec = get_bootstrap_spec(name, admin_secret) |
1121 | + cfgfile = txjuju.prepare_for_bootstrap(spec, self.version, cfgdir) |
1122 | + cli = self.cli(cfgdir) |
1123 | + cli.bootstrap(spec, cfgfile=cfgfile) |
1124 | + api_info = cli.api_info(spec.name) |
1125 | + return cli, api_info |
1126 | + |
1127 | + def is_bootstrapped(self): |
1128 | + """Return True if a fake-juju controller is running.""" |
1129 | + return os.path.exists(self.fifo) |
1130 | |
1131 | === modified file 'python/fakejuju/testing.py' |
1132 | --- python/fakejuju/testing.py 2016-10-06 22:51:41 +0000 |
1133 | +++ python/fakejuju/testing.py 2016-10-20 21:22:41 +0000 |
1134 | @@ -1,6 +1,5 @@ |
1135 | # Copyright 2016 Canonical Limited. All rights reserved. |
1136 | |
1137 | -import txjuju |
1138 | from fixtures import Fixture, TempDir |
1139 | from testtools.content import content_from_file |
1140 | |
1141 | @@ -40,29 +39,16 @@ |
1142 | def setUp(self): |
1143 | super(FakeJujuFixture, self).setUp() |
1144 | self._juju_home = self.useFixture(TempDir()) |
1145 | - self._juju = fakejuju.FakeJuju.make( |
1146 | - self._juju_home.path, self._version, self._logs_dir) |
1147 | + self.fakejuju = fakejuju.FakeJuju.from_version( |
1148 | + self._version, self._juju_home.path, self._logs_dir) |
1149 | |
1150 | if not self._logs_dir: |
1151 | # Attach logs as testtools details. |
1152 | self.addDetail("log-file", content_from_file(self._juju.logfile)) |
1153 | |
1154 | - spec = fakejuju.get_bootstrap_spec(self._controller, self._password) |
1155 | - cfgfile = txjuju.prepare_for_bootstrap( |
1156 | - spec, self._version, self._juju_home) |
1157 | - cli = self._juju.cli() |
1158 | - cli.bootstrap(spec, cfgfile=cfgfile) |
1159 | - api_info = cli.api_info(spec.name) |
1160 | - if self._version.startswith("1."): |
1161 | - # fake-juju doesn't give us the password, so we have to |
1162 | - # set it here. |
1163 | - api_info = api_info._replace(password=self._password) |
1164 | - self.api_info = api_info |
1165 | + self._juju, self.all_api_info = self.fakejuju.bootstrap( |
1166 | + self._controller, self._password) |
1167 | |
1168 | def cleanUp(self): |
1169 | self._juju.destroy_controller(self._controller) |
1170 | super(FakeJujuFixture, self).cleanUp() |
1171 | - |
1172 | - def add_failure(self, entity): |
1173 | - """Make the given entity fail with an error status.""" |
1174 | - self._juju.failures.fail_entity(entity) |
1175 | |
1176 | === modified file 'python/fakejuju/tests/test_fakejuju.py' |
1177 | --- python/fakejuju/tests/test_fakejuju.py 2016-10-17 15:36:15 +0000 |
1178 | +++ python/fakejuju/tests/test_fakejuju.py 2016-10-20 21:22:41 +0000 |
1179 | @@ -1,10 +1,15 @@ |
1180 | # Copyright 2016 Canonical Limited. All rights reserved. |
1181 | |
1182 | +from contextlib import contextmanager |
1183 | import os |
1184 | +import shutil |
1185 | +import tempfile |
1186 | import unittest |
1187 | |
1188 | from txjuju import _juju1, _juju2 |
1189 | from txjuju._utils import Executable |
1190 | +import txjuju.cli |
1191 | +import yaml |
1192 | |
1193 | from fakejuju.failures import Failures |
1194 | from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju |
1195 | @@ -44,9 +49,10 @@ |
1196 | def test_all_args(self): |
1197 | """set_envvars() works correctly when given all args.""" |
1198 | envvars = {} |
1199 | - set_envvars(envvars, "/spam/failures", "/eggs/logsdir") |
1200 | + set_envvars(envvars, "/spam", "/spam/failures", "/eggs/logsdir") |
1201 | |
1202 | self.assertEqual(envvars, { |
1203 | + "FAKE_JUJU_DATA_DIR": "/spam", |
1204 | "FAKE_JUJU_FAILURES": "/spam/failures", |
1205 | "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir", |
1206 | }) |
1207 | @@ -57,6 +63,7 @@ |
1208 | set_envvars(envvars) |
1209 | |
1210 | self.assertEqual(envvars, { |
1211 | + "FAKE_JUJU_DATA_DIR": "", |
1212 | "FAKE_JUJU_FAILURES": "", |
1213 | "FAKE_JUJU_LOGS_DIR": "", |
1214 | }) |
1215 | @@ -64,9 +71,10 @@ |
1216 | def test_start_empty(self): |
1217 | """set_envvars() sets all values on an empty dict.""" |
1218 | envvars = {} |
1219 | - set_envvars(envvars, "x", "y") |
1220 | + set_envvars(envvars, "w", "x", "y") |
1221 | |
1222 | self.assertEqual(envvars, { |
1223 | + "FAKE_JUJU_DATA_DIR": "w", |
1224 | "FAKE_JUJU_FAILURES": "x", |
1225 | "FAKE_JUJU_LOGS_DIR": "y", |
1226 | }) |
1227 | @@ -74,10 +82,11 @@ |
1228 | def test_no_collisions(self): |
1229 | """set_envvars() sets all values when none are set yet.""" |
1230 | envvars = {"SPAM": "eggs"} |
1231 | - set_envvars(envvars, "x", "y") |
1232 | + set_envvars(envvars, "w", "x", "y") |
1233 | |
1234 | self.assertEqual(envvars, { |
1235 | "SPAM": "eggs", |
1236 | + "FAKE_JUJU_DATA_DIR": "w", |
1237 | "FAKE_JUJU_FAILURES": "x", |
1238 | "FAKE_JUJU_LOGS_DIR": "y", |
1239 | }) |
1240 | @@ -85,12 +94,14 @@ |
1241 | def test_empty_to_nonempty(self): |
1242 | """set_envvars() updates empty values.""" |
1243 | envvars = { |
1244 | + "FAKE_JUJU_DATA_DIR": "", |
1245 | "FAKE_JUJU_FAILURES": "", |
1246 | "FAKE_JUJU_LOGS_DIR": "", |
1247 | } |
1248 | - set_envvars(envvars, "x", "y") |
1249 | + set_envvars(envvars, "w", "x", "y") |
1250 | |
1251 | self.assertEqual(envvars, { |
1252 | + "FAKE_JUJU_DATA_DIR": "w", |
1253 | "FAKE_JUJU_FAILURES": "x", |
1254 | "FAKE_JUJU_LOGS_DIR": "y", |
1255 | }) |
1256 | @@ -98,12 +109,14 @@ |
1257 | def test_nonempty_to_nonempty(self): |
1258 | """set_envvars() overwrites existing values.""" |
1259 | envvars = { |
1260 | + "FAKE_JUJU_DATA_DIR": "spam", |
1261 | "FAKE_JUJU_FAILURES": "spam", |
1262 | "FAKE_JUJU_LOGS_DIR": "ham", |
1263 | } |
1264 | - set_envvars(envvars, "x", "y") |
1265 | + set_envvars(envvars, "w", "x", "y") |
1266 | |
1267 | self.assertEqual(envvars, { |
1268 | + "FAKE_JUJU_DATA_DIR": "w", |
1269 | "FAKE_JUJU_FAILURES": "x", |
1270 | "FAKE_JUJU_LOGS_DIR": "y", |
1271 | }) |
1272 | @@ -111,12 +124,14 @@ |
1273 | def test_nonempty_to_empty(self): |
1274 | """set_envvars() with no args "unsets" existing values.""" |
1275 | envvars = { |
1276 | + "FAKE_JUJU_DATA_DIR": "w", |
1277 | "FAKE_JUJU_FAILURES": "x", |
1278 | "FAKE_JUJU_LOGS_DIR": "y", |
1279 | } |
1280 | set_envvars(envvars) |
1281 | |
1282 | self.assertEqual(envvars, { |
1283 | + "FAKE_JUJU_DATA_DIR": "", |
1284 | "FAKE_JUJU_FAILURES": "", |
1285 | "FAKE_JUJU_LOGS_DIR": "", |
1286 | }) |
1287 | @@ -131,7 +146,7 @@ |
1288 | |
1289 | self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6") |
1290 | self.assertEqual(juju.version, "1.25.6") |
1291 | - self.assertEqual(juju.cfgdir, "/a/juju/home") |
1292 | + self.assertEqual(juju.datadir, "/a/juju/home") |
1293 | self.assertEqual(juju.logsdir, "/logs/dir") |
1294 | self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures") |
1295 | |
1296 | @@ -141,19 +156,20 @@ |
1297 | |
1298 | self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6") |
1299 | self.assertEqual(juju.version, "1.25.6") |
1300 | - self.assertEqual(juju.cfgdir, "/my/juju/home") |
1301 | + self.assertEqual(juju.datadir, "/my/juju/home") |
1302 | self.assertEqual(juju.logsdir, "/my/juju/home") |
1303 | self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures") |
1304 | |
1305 | def test_full(self): |
1306 | """FakeJuju() works correctly when given all args.""" |
1307 | - cfgdir = "/my/juju/home" |
1308 | - failures = Failures(cfgdir) |
1309 | - juju = FakeJuju("/fake-juju", "1.25.6", cfgdir, "/some/logs", failures) |
1310 | + datadir = "/my/juju/home" |
1311 | + failures = Failures(datadir) |
1312 | + juju = FakeJuju( |
1313 | + "/fake-juju", "1.25.6", datadir, "/some/logs", failures) |
1314 | |
1315 | self.assertEqual(juju.filename, "/fake-juju") |
1316 | self.assertEqual(juju.version, "1.25.6") |
1317 | - self.assertEqual(juju.cfgdir, cfgdir) |
1318 | + self.assertEqual(juju.datadir, datadir) |
1319 | self.assertEqual(juju.logsdir, "/some/logs") |
1320 | self.assertIs(juju.failures, failures) |
1321 | |
1322 | @@ -163,7 +179,7 @@ |
1323 | |
1324 | self.assertEqual(juju.filename, "/fake-juju") |
1325 | self.assertEqual(juju.version, "1.25.6") |
1326 | - self.assertEqual(juju.cfgdir, "/my/juju/home") |
1327 | + self.assertEqual(juju.datadir, "/my/juju/home") |
1328 | self.assertEqual(juju.logsdir, "/my/juju/home") |
1329 | self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures") |
1330 | |
1331 | @@ -174,7 +190,7 @@ |
1332 | juju_unicode = FakeJuju( |
1333 | u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/...")) |
1334 | |
1335 | - for name in ('filename version cfgdir logsdir'.split()): |
1336 | + for name in ('filename version datadir logsdir'.split()): |
1337 | self.assertIsInstance(getattr(juju_str, name), str) |
1338 | self.assertIsInstance(getattr(juju_unicode, name), unicode) |
1339 | |
1340 | @@ -192,8 +208,8 @@ |
1341 | with self.assertRaises(ValueError): |
1342 | FakeJuju("/fake-juju", "", "/my/juju/home") |
1343 | |
1344 | - def test_missing_cfgdir(self): |
1345 | - """FakeJuju() fails if cfgdir is None or empty.""" |
1346 | + def test_missing_datadir(self): |
1347 | + """FakeJuju() fails if datadir is None or empty.""" |
1348 | with self.assertRaises(ValueError): |
1349 | FakeJuju("/fake-juju", "1.25.6", None) |
1350 | with self.assertRaises(ValueError): |
1351 | @@ -226,44 +242,131 @@ |
1352 | def test_cli_full(self): |
1353 | """FakeJuju.cli() works correctly when given all args.""" |
1354 | juju = FakeJuju("/fake-juju", "1.25.6", "/x") |
1355 | - cli = juju.cli({"SPAM": "eggs"}) |
1356 | + cli = juju.cli("/y", {"SPAM": "eggs"}) |
1357 | |
1358 | self.assertEqual( |
1359 | cli._exe, |
1360 | Executable("/fake-juju", { |
1361 | "SPAM": "eggs", |
1362 | + "FAKE_JUJU_DATA_DIR": "/x", |
1363 | "FAKE_JUJU_FAILURES": "/x/juju-failures", |
1364 | "FAKE_JUJU_LOGS_DIR": "/x", |
1365 | - "JUJU_HOME": "/x", |
1366 | + "JUJU_HOME": "/y", |
1367 | }), |
1368 | ) |
1369 | |
1370 | def test_cli_minimal(self): |
1371 | """FakeJuju.cli() works correctly when given minimal args.""" |
1372 | juju = FakeJuju("/fake-juju", "1.25.6", "/x") |
1373 | - cli = juju.cli() |
1374 | + cli = juju.cli("/y") |
1375 | |
1376 | self.assertEqual( |
1377 | cli._exe, |
1378 | Executable("/fake-juju", dict(os.environ, **{ |
1379 | + "FAKE_JUJU_DATA_DIR": "/x", |
1380 | "FAKE_JUJU_FAILURES": "/x/juju-failures", |
1381 | "FAKE_JUJU_LOGS_DIR": "/x", |
1382 | - "JUJU_HOME": "/x", |
1383 | + "JUJU_HOME": "/y", |
1384 | })), |
1385 | ) |
1386 | |
1387 | def test_cli_juju1(self): |
1388 | """FakeJuju.cli() works correctly for Juju 1.x.""" |
1389 | juju = FakeJuju.from_version("1.25.6", "/x") |
1390 | - cli = juju.cli() |
1391 | + cli = juju.cli("/y") |
1392 | |
1393 | - self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/x") |
1394 | + self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/y") |
1395 | self.assertIsInstance(cli._juju, _juju1.CLIHooks) |
1396 | |
1397 | def test_cli_juju2(self): |
1398 | """FakeJuju.cli() works correctly for Juju 2.x.""" |
1399 | juju = FakeJuju.from_version("2.0.0", "/x") |
1400 | - cli = juju.cli() |
1401 | + cli = juju.cli("/y") |
1402 | |
1403 | - self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/x") |
1404 | + self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/y") |
1405 | self.assertIsInstance(cli._juju, _juju2.CLIHooks) |
1406 | + |
1407 | + def test_bootstrap(self): |
1408 | + """FakeJuju.bootstrap() bootstraps from scratch using fake-juju.""" |
1409 | + with tempdir() as datadir: |
1410 | + fakejuju = FakeJuju.from_version("1.25.6", datadir) |
1411 | + cfgdir = os.path.join(datadir, "juju") |
1412 | + cli, api_info = fakejuju.bootstrap("spam", cfgdir, "secret") |
1413 | + port = api_info[None].address.split(":")[-1] |
1414 | + |
1415 | + files = os.listdir(datadir) |
1416 | + files.extend(os.path.join("juju", name) |
1417 | + for name in os.listdir(cfgdir)) |
1418 | + files.sort() |
1419 | + with open(os.path.join(cfgdir, "environments.yaml")) as envfile: |
1420 | + data = envfile.read() |
1421 | + |
1422 | + cli.destroy_controller() |
1423 | + |
1424 | + self.maxDiff = None |
1425 | + self.assertEqual(api_info, { |
1426 | + 'controller': txjuju.cli.APIInfo( |
1427 | + endpoints=['localhost:' + port], |
1428 | + user='admin', |
1429 | + password='secret', |
1430 | + model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d', |
1431 | + ), |
1432 | + None: txjuju.cli.APIInfo( |
1433 | + endpoints=['localhost:' + port], |
1434 | + user='admin', |
1435 | + password='secret', |
1436 | + model_uuid=None, |
1437 | + ), |
1438 | + }) |
1439 | + self.assertItemsEqual(files, [ |
1440 | + 'cert.ca', |
1441 | + 'fake-juju.log', |
1442 | + 'fakejuju', |
1443 | + 'fifo', |
1444 | + 'juju', |
1445 | + 'juju/environments', |
1446 | + 'juju/environments.yaml', |
1447 | + ]) |
1448 | + self.assertEqual(yaml.load(data), { |
1449 | + "environments": { |
1450 | + "spam": { |
1451 | + "admin-secret": "secret", |
1452 | + "default-series": "trusty", |
1453 | + "type": "dummy", |
1454 | + }, |
1455 | + }, |
1456 | + }) |
1457 | + |
1458 | + def test_is_bootstrapped_true(self): |
1459 | + """FakeJuju.is_bootstrapped() returns True if the fifo file exists.""" |
1460 | + with tempdir() as datadir: |
1461 | + fakejuju = FakeJuju.from_version("1.25.6", datadir) |
1462 | + with open(fakejuju.fifo, "w"): |
1463 | + pass |
1464 | + result = fakejuju.is_bootstrapped() |
1465 | + |
1466 | + self.assertTrue(result) |
1467 | + |
1468 | + def test_is_bootstrapped_false(self): |
1469 | + """FakeJuju.is_bootstrapped() returns False if the fifo is gone.""" |
1470 | + with tempdir() as datadir: |
1471 | + fakejuju = FakeJuju.from_version("1.25.6", datadir) |
1472 | + result = fakejuju.is_bootstrapped() |
1473 | + |
1474 | + self.assertFalse(result) |
1475 | + |
1476 | + def test_is_bootstrapped_datadir_missing(self): |
1477 | + """FakeJuju.is_bootstrapped() returns False if the data dir is gone.""" |
1478 | + fakejuju = FakeJuju.from_version("1.25.6", "/tmp/fakejuju-no-exist") |
1479 | + result = fakejuju.is_bootstrapped() |
1480 | + |
1481 | + self.assertFalse(result) |
1482 | + |
1483 | + |
1484 | +@contextmanager |
1485 | +def tempdir(): |
1486 | + cfgdir = tempfile.mkdtemp("fakejuju-test-") |
1487 | + try: |
1488 | + yield cfgdir |
1489 | + finally: |
1490 | + shutil.rmtree(cfgdir) |