Merge lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir into lp:~landscape/fake-juju/trunk-old

Proposed by Eric Snow
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
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.

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 53
Branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Jenkins: https://ci.lscape.net/job/latch-test-xenial/310/

review: Approve (test results)
54. By Eric Snow

Merge from parent.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 54
Branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Jenkins: https://ci.lscape.net/job/latch-test-xenial/313/

review: Approve (test results)
Revision history for this message
Chad Smith (chad.smith) :
review: Approve
Revision history for this message
Данило Шеган (danilo) wrote :

Looks good, tests pass. +1

review: Approve
Revision history for this message
Eric Snow (ericsnowcurrently) :
55. By Eric Snow

Clarify some names.

56. By Eric Snow

Add missing doc comments.

57. By Eric Snow

Clarify some names.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 57
Branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Jenkins: https://ci.lscape.net/job/latch-test-xenial/332/

review: Approve (test results)
58. By Eric Snow

Merge from parent.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 58
Branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Jenkins: https://ci.lscape.net/job/latch-test-xenial/333/

review: Approve (test results)
59. By Eric Snow

Merge from trunk.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: make ci-test
Result: Success
Revno: 59
Branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Jenkins: https://ci.lscape.net/job/latch-test-xenial/346/

review: Approve (test results)

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-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

Subscribers

People subscribed via source and target branches

to all changes: