Merge lp:~ericsnowcurrently/fake-juju/testing-fixes into lp:~landscape/fake-juju/trunk-old

Proposed by Eric Snow
Status: Superseded
Proposed branch: lp:~ericsnowcurrently/fake-juju/testing-fixes
Merge into: lp:~landscape/fake-juju/trunk-old
Diff against target: 1490 lines (+645/-307)
7 files modified
1.25.6/fake-juju.go (+236/-105)
2.0-beta17/fake-juju.go (+233/-136)
Makefile (+1/-1)
python/Makefile (+1/-1)
python/fakejuju/fakejuju.py (+44/-23)
python/fakejuju/testing.py (+4/-18)
python/fakejuju/tests/test_fakejuju.py (+126/-23)
To merge this branch: bzr merge lp:~ericsnowcurrently/fake-juju/testing-fixes
Reviewer Review Type Date Requested Status
Landscape Pending
Landscape Pending
Review via email: mp+308971@code.launchpad.net

This proposal has been superseded by a proposal from 2016-10-20.

Description of the change

Fix a couple of broken behaviors for testing.

Without these fixes fake-juju can cause problems in tests.

To post a comment you must log in.
60. By Eric Snow

Merge from parent.

61. By Eric Snow

Fix a rename.

62. By Eric Snow

Merge from parent.

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: