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