Merge lp:~ericsnowcurrently/fake-juju/juju-2.0-support into lp:~landscape/fake-juju/trunk-old

Proposed by Eric Snow
Status: Superseded
Proposed branch: lp:~ericsnowcurrently/fake-juju/juju-2.0-support
Merge into: lp:~landscape/fake-juju/trunk-old
Diff against target: 2443 lines (+826/-902)
11 files modified
1.24.7/fake-juju.go (+0/-514)
1.25.6/fake-juju.go (+275/-109)
2.0.0/fake-juju.go (+287/-156)
Makefile (+3/-3)
patches/juju-core_1.24.7.patch (+0/-47)
patches/juju-core_2.0.0.patch (+6/-6)
python/Makefile (+1/-1)
python/fakejuju/fakejuju.py (+44/-23)
python/fakejuju/testing.py (+5/-19)
python/fakejuju/tests/test_fakejuju.py (+204/-23)
tests/test_fake.py (+1/-1)
To merge this branch: bzr merge lp:~ericsnowcurrently/fake-juju/juju-2.0-support
Reviewer Review Type Date Requested Status
🤖 Landscape Builder test results Approve
Landscape Pending
Landscape Pending
Review via email: mp+309284@code.launchpad.net

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

Description of the change

Update to juju 2.0.0.

Also drop support for 1.24.x.

Testing instructions:

Run make test

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: 63
Branch: lp:~ericsnowcurrently/fake-juju/juju-2.0-support
Jenkins: https://ci.lscape.net/job/latch-test-xenial/336/

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

Add some comments about bootstrap.

65. By Eric Snow

Factor out waitForBootstrapCompletion().

66. By Eric Snow

Rename the command handler functions.

67. By Eric Snow

Factor out destroyController().

68. By Eric Snow

Destroy the controller if bootstrap fails.

69. By Eric Snow

Factor out updateBootstrapResult().

70. By Eric Snow

Call copyConfig() before updating the bootstrap result.

71. By Eric Snow

Handle bootstrap error cleanup centrally.

72. By Eric Snow

Capture error output from the daemon.

73. By Eric Snow

Minor touch-ups to SetUpTest().

74. By Eric Snow

Use constants for the environment variable names.

75. By Eric Snow

Add a log file for jujud logs.

76. By Eric Snow

Factor out reportInfo().

77. By Eric Snow

Ensure that the daemon runs with FAKE_JUJU_DATA_DIR set.

78. By Eric Snow

Parse all the supported "juju bootstrap" args.

79. By Eric Snow

Always ensure that the Juju cfg dir exists.

80. By Eric Snow

Support a -v bootstrap arg.

81. By Eric Snow

Set up logging right *after* calling JujuConnSuite.SetUpTest().

82. By Eric Snow

Fix bootstrap arg order in a test.

83. By Eric Snow

Merge from trunk.

84. By Eric Snow

Copy some of the 2.0.0 tweaks over to 1.25.6.

85. By Eric Snow

Default to Juju 2.x in tests.

86. By Eric Snow

Pull a little more code from 2.0.0.

Unmerged revisions

Preview Diff

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

Subscribers

People subscribed via source and target branches

to all changes: