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
=== removed directory '1.24.7'
=== removed file '1.24.7/fake-juju.go'
--- 1.24.7/fake-juju.go 2016-06-10 17:08:28 +0000
+++ 1.24.7/fake-juju.go 1970-01-01 00:00:00 +0000
@@ -1,514 +0,0 @@
1package main
2
3import (
4 "bufio"
5 "encoding/json"
6 "errors"
7 "fmt"
8 gc "gopkg.in/check.v1"
9 "io"
10 "io/ioutil"
11 "log"
12 "os"
13 "os/exec"
14 "path/filepath"
15 "strings"
16 "syscall"
17 "testing"
18 "time"
19
20 "github.com/juju/juju/agent"
21 "github.com/juju/juju/api"
22 "github.com/juju/juju/environs"
23 "github.com/juju/juju/environs/configstore"
24 "github.com/juju/juju/instance"
25 "github.com/juju/juju/juju/osenv"
26 jujutesting "github.com/juju/juju/juju/testing"
27 "github.com/juju/juju/network"
28 _ "github.com/juju/juju/provider/maas"
29 "github.com/juju/juju/state"
30 coretesting "github.com/juju/juju/testing"
31 "github.com/juju/juju/testing/factory"
32 "github.com/juju/juju/version"
33 "github.com/juju/names"
34 corecharm "gopkg.in/juju/charm.v5/charmrepo"
35 goyaml "gopkg.in/yaml.v1"
36)
37
38func main() {
39 if len(os.Args) > 1 {
40 code := 0
41 err := handleCommand(os.Args[1])
42 if err != nil {
43 fmt.Println(err.Error())
44 code = 1
45 }
46 os.Exit(code)
47 }
48 t := &testing.T{}
49 coretesting.MgoTestPackage(t)
50}
51
52type processInfo struct {
53 WorkDir string
54 EndpointAddr string
55 Uuid string
56 CACert string
57}
58
59func handleCommand(command string) error {
60 if command == "bootstrap" {
61 return bootstrap()
62 }
63 if command == "api-endpoints" {
64 return apiEndpoints()
65 }
66 if command == "api-info" {
67 return apiInfo()
68 }
69 if command == "destroy-environment" {
70 return destroyEnvironment()
71 }
72 return errors.New("command not found")
73}
74
75func bootstrap() error {
76 envName, password, err := environmentNameAndPassword()
77 if err != nil {
78 return err
79 }
80 command := exec.Command(os.Args[0])
81 command.Env = os.Environ()
82 command.Env = append(command.Env, "ADMIN_PASSWORD="+password)
83 stdout, err := command.StdoutPipe()
84 if err != nil {
85 return err
86 }
87 command.Start()
88 apiInfo, err := parseApiInfo(envName, stdout)
89 if err != nil {
90 return err
91 }
92 dialOpts := api.DialOpts{
93 DialAddressInterval: 50 * time.Millisecond,
94 Timeout: 5 * time.Second,
95 RetryDelay: 2 * time.Second,
96 }
97 state, err := api.Open(apiInfo, dialOpts)
98 if err != nil {
99 return err
100 }
101 client := state.Client()
102 watcher, err := client.WatchAll()
103 if err != nil {
104 return err
105 }
106 deltas, err := watcher.Next()
107 if err != nil {
108 return err
109 }
110 for _, delta := range deltas {
111 entityId := delta.Entity.EntityId()
112 if entityId.Kind == "machine" {
113 machineId, _ := entityId.Id.(string)
114 if machineId == "0" {
115 return nil
116 }
117 }
118 }
119 return errors.New("invalid delta")
120}
121
122func apiEndpoints() error {
123 info, err := readProcessInfo()
124 if err != nil {
125 return err
126 }
127 fmt.Println(info.EndpointAddr)
128 return nil
129}
130
131func apiInfo() error {
132 info, err := readProcessInfo()
133 if err != nil {
134 return err
135 }
136 fmt.Printf("{\"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", info.Uuid, info.EndpointAddr)
137 return nil
138}
139
140func destroyEnvironment() error {
141 info, err := readProcessInfo()
142 if err != nil {
143 return err
144 }
145 fifoPath := filepath.Join(info.WorkDir, "fifo")
146 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)
147 if err != nil {
148 return err
149 }
150 defer fd.Close()
151 _, err = fd.WriteString("destroy\n")
152 if err != nil {
153 return err
154 }
155 return nil
156}
157
158func environmentNameAndPassword() (string, string, error) {
159 jujuHome := os.Getenv("JUJU_HOME")
160 osenv.SetJujuHome(jujuHome)
161 environs, err := environs.ReadEnvirons(
162 filepath.Join(jujuHome, "environments.yaml"))
163 if err != nil {
164 return "", "", err
165 }
166 envName := environs.Names()[0]
167 config, err := environs.Config(envName)
168 if err != nil {
169 return "", "", err
170 }
171 return envName, config.AdminSecret(), nil
172}
173
174func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {
175 buffer := bufio.NewReader(stdout)
176 line, _, err := buffer.ReadLine()
177 if err != nil {
178 return nil, err
179 }
180 uuid := string(line)
181 environTag := names.NewEnvironTag(uuid)
182 line, _, err = buffer.ReadLine()
183 if err != nil {
184 return nil, err
185 }
186 workDir := string(line)
187 store, err := configstore.NewDisk(workDir)
188 if err != nil {
189 return nil, err
190 }
191 info, err := store.ReadInfo("dummyenv")
192 if err != nil {
193 return nil, err
194 }
195 credentials := info.APICredentials()
196 endpoint := info.APIEndpoint()
197 addresses := endpoint.Addresses
198 apiInfo := &api.Info{
199 Addrs: addresses,
200 Tag: names.NewLocalUserTag(credentials.User),
201 Password: credentials.Password,
202 CACert: endpoint.CACert,
203 EnvironTag: environTag,
204 }
205 err = writeProcessInfo(envName, &processInfo{
206 WorkDir: workDir,
207 EndpointAddr: addresses[0],
208 Uuid: uuid,
209 CACert: endpoint.CACert,
210 })
211 if err != nil {
212 return nil, err
213 }
214 return apiInfo, nil
215}
216
217func readProcessInfo() (*processInfo, error) {
218 infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju")
219 data, err := ioutil.ReadFile(infoPath)
220 if err != nil {
221 return nil, err
222 }
223 info := &processInfo{}
224 err = goyaml.Unmarshal(data, info)
225 if err != nil {
226 return nil, err
227 }
228 return info, nil
229}
230
231func writeProcessInfo(envName string, info *processInfo) error {
232 jujuHome := os.Getenv("JUJU_HOME")
233 infoPath := filepath.Join(jujuHome, "fakejuju")
234 logPath := filepath.Join(jujuHome, "fake-juju.log")
235 caCertPath := filepath.Join(jujuHome, "cert.ca")
236 envPath := filepath.Join(jujuHome, "environments")
237 os.Mkdir(envPath, 0755)
238 jEnvPath := filepath.Join(envPath, envName+".jenv")
239 data, _ := goyaml.Marshal(info)
240 err := os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath)
241 if err != nil {
242 return err
243 }
244 err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath)
245 if err != nil {
246 return err
247 }
248 err = ioutil.WriteFile(infoPath, data, 0644)
249 if err != nil {
250 return err
251 }
252 return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644)
253}
254
255type FakeJujuSuite struct {
256 jujutesting.JujuConnSuite
257
258 instanceCount int
259 machineStarted map[string]bool
260 fifoPath string
261 logFile *os.File
262}
263
264var _ = gc.Suite(&FakeJujuSuite{})
265
266func (s *FakeJujuSuite) SetUpTest(c *gc.C) {
267 var CommandOutput = (*exec.Cmd).CombinedOutput
268 s.JujuConnSuite.SetUpTest(c)
269
270 ports := s.APIState.APIHostPorts()
271 ports[0][0].NetworkName = "dummy-provider-network"
272 err := s.State.SetAPIHostPorts(ports)
273 c.Assert(err, gc.IsNil)
274
275 s.machineStarted = make(map[string]bool)
276 s.PatchValue(&corecharm.CacheDir, c.MkDir())
277 password := "dummy-password"
278 if os.Getenv("ADMIN_PASSWORD") != "" {
279 password = os.Getenv("ADMIN_PASSWORD")
280 }
281 _, err = s.State.AddUser("admin", "Admin", password, "dummy-admin")
282 c.Assert(err, gc.IsNil)
283 _, err = s.State.AddEnvironmentUser(
284 names.NewLocalUserTag("admin"), names.NewLocalUserTag("dummy-admin"), "Admin")
285 c.Assert(err, gc.IsNil)
286
287 // Create a machine to manage the environment.
288 stateServer := s.Factory.MakeMachine(c, &factory.MachineParams{
289 InstanceId: s.newInstanceId(),
290 Nonce: agent.BootstrapNonce,
291 Jobs: []state.MachineJob{state.JobManageEnviron, state.JobHostUnits},
292 Series: "trusty",
293 })
294 c.Assert(stateServer.SetAgentVersion(version.Current), gc.IsNil)
295 address := network.NewScopedAddress("127.0.0.1", network.ScopeCloudLocal)
296 c.Assert(stateServer.SetProviderAddresses(address), gc.IsNil)
297 c.Assert(stateServer.SetStatus(state.StatusStarted, "", nil), gc.IsNil)
298 _, err = stateServer.SetAgentPresence()
299 c.Assert(err, gc.IsNil)
300 s.State.StartSync()
301 err = stateServer.WaitAgentPresence(coretesting.LongWait)
302 c.Assert(err, gc.IsNil)
303
304 apiInfo := s.APIInfo(c)
305 //fmt.Println(apiInfo.Addrs[0])
306 jujuHome := osenv.JujuHome()
307 fmt.Println(apiInfo.EnvironTag.Id())
308 fmt.Println(jujuHome)
309
310 binPath := filepath.Join(jujuHome, "bin")
311 os.Mkdir(binPath, 0755)
312 fakeSSHData := []byte("#!/bin/sh\nsleep 1\n")
313 fakeSSHPath := filepath.Join(binPath, "ssh")
314 err = ioutil.WriteFile(fakeSSHPath, fakeSSHData, 0755)
315 c.Assert(err, gc.IsNil)
316 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
317
318 s.fifoPath = filepath.Join(jujuHome, "fifo")
319 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)
320
321 // Logging
322 logPath := filepath.Join(jujuHome, "fake-juju.log")
323 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
324 c.Assert(err, gc.IsNil)
325
326 log.SetOutput(s.logFile)
327 dpkgCmd := exec.Command(
328 "dpkg-query", "--showformat='${Version}'", "--show", "fake-juju")
329 out, err := CommandOutput(dpkgCmd)
330 fakeJujuDebVersion := strings.Trim(string(out), "'")
331 log.Printf("Started fake-juju-%s for %s\nJUJU_HOME=%s", fakeJujuDebVersion, version.Current, jujuHome)
332
333}
334
335func (s *FakeJujuSuite) TearDownTest(c *gc.C) {
336 s.JujuConnSuite.TearDownTest(c)
337 s.logFile.Close()
338}
339
340func (s *FakeJujuSuite) TestStart(c *gc.C) {
341 watcher := s.State.Watch()
342 go func() {
343 fd, err := os.Open(s.fifoPath)
344 c.Assert(err, gc.IsNil)
345 scanner := bufio.NewScanner(fd)
346 scanner.Scan()
347 watcher.Stop()
348 }()
349 for {
350 deltas, err := watcher.Next()
351 log.Println("Got deltas")
352 if err != nil {
353 if err.Error() == "watcher was stopped" {
354 log.Println("Watcher stopped")
355 break
356 }
357 log.Println("Unexpected error", err.Error())
358 }
359 c.Assert(err, gc.IsNil)
360 for _, d := range deltas {
361
362 entity, err := json.MarshalIndent(d.Entity, "", " ")
363 c.Assert(err, gc.IsNil)
364 verb := "change"
365 if d.Removed {
366 verb = "remove"
367 }
368 log.Println("Processing delta", verb, d.Entity.EntityId().Kind, string(entity[:]))
369
370 entityId := d.Entity.EntityId()
371 if entityId.Kind == "machine" {
372 machineId, ok := entityId.Id.(string)
373 c.Assert(ok, gc.Equals, true)
374 c.Assert(s.handleAddMachine(machineId), gc.IsNil)
375 }
376 if entityId.Kind == "unit" {
377 unitId, ok := entityId.Id.(string)
378 c.Assert(ok, gc.Equals, true)
379 c.Assert(s.handleAddUnit(unitId), gc.IsNil)
380 }
381 log.Println("Done processing delta")
382 }
383 }
384}
385
386func (s *FakeJujuSuite) handleAddMachine(id string) error {
387 machine, err := s.State.Machine(id)
388 if err != nil {
389 return err
390 }
391 if instanceId, _ := machine.InstanceId(); instanceId == "" {
392 err = machine.SetProvisioned(s.newInstanceId(), agent.BootstrapNonce, nil)
393 if err != nil {
394 log.Println("Got error with SetProvisioned", err)
395 return err
396 }
397 address := network.NewScopedAddress("127.0.0.1", network.ScopeCloudLocal)
398 err = machine.SetProviderAddresses(address)
399 if err != nil {
400 log.Println("Got error with SetProviderAddresses", err)
401 return err
402 }
403 }
404 status, _ := machine.Status()
405 if status.Status == state.StatusPending {
406 if err = s.startMachine(machine); err != nil {
407 log.Println("Got error with startMachine:", err)
408 return err
409 }
410 } else if status.Status == state.StatusStarted {
411 if _, ok := s.machineStarted[id]; !ok {
412 s.machineStarted[id] = true
413 if err = s.startUnits(machine); err != nil {
414 log.Println("Got error with startUnits", err)
415 return err
416 }
417 }
418 }
419 return nil
420}
421
422func (s *FakeJujuSuite) handleAddUnit(id string) error {
423 unit, err := s.State.Unit(id)
424 if err != nil {
425 log.Println("Got error with get unit", err)
426 return err
427 }
428 machineId, err := unit.AssignedMachineId()
429 if err != nil {
430 return nil
431 }
432 log.Println("Got machineId", machineId)
433 machine, err := s.State.Machine(machineId)
434 if err != nil {
435 log.Println("Got error with unit AssignedMachineId", err)
436 return err
437 }
438 machineStatus, _ := machine.Status()
439 if machineStatus.Status != state.StatusStarted {
440 return nil
441 }
442 status, _ := unit.Status()
443 if status.Status != state.StatusActive {
444 if err = s.startUnit(unit); err != nil {
445 return err
446 }
447 }
448 return nil
449}
450
451func (s *FakeJujuSuite) startMachine(machine *state.Machine) error {
452 time.Sleep(500 * time.Millisecond)
453 err := machine.SetStatus(state.StatusStarted, "", nil)
454 if err != nil {
455 return err
456 }
457 err = machine.SetAgentVersion(version.Current)
458 if err != nil {
459 return err
460 }
461 _, err = machine.SetAgentPresence()
462 if err != nil {
463 return err
464 }
465 s.State.StartSync()
466 err = machine.WaitAgentPresence(coretesting.LongWait)
467 if err != nil {
468 return err
469 }
470 return nil
471}
472
473func (s *FakeJujuSuite) startUnits(machine *state.Machine) error {
474 units, err := machine.Units()
475 if err != nil {
476 return err
477 }
478 return nil
479 for _, unit := range units {
480 unitStatus, _ := unit.Status()
481 if unitStatus.Status != state.StatusActive {
482 if err = s.startUnit(unit); err != nil {
483 return err
484 }
485 }
486 }
487 return nil
488}
489
490func (s *FakeJujuSuite) startUnit(unit *state.Unit) error {
491 err := unit.SetStatus(state.StatusActive, "", nil)
492 if err != nil {
493 return err
494 }
495 _, err = unit.SetAgentPresence()
496 if err != nil {
497 return err
498 }
499 s.State.StartSync()
500 err = unit.WaitAgentPresence(coretesting.LongWait)
501 if err != nil {
502 return err
503 }
504 err = unit.SetAgentStatus(state.StatusIdle, "", nil)
505 if err != nil {
506 return err
507 }
508 return nil
509}
510
511func (s *FakeJujuSuite) newInstanceId() instance.Id {
512 s.instanceCount += 1
513 return instance.Id(fmt.Sprintf("id-%d", s.instanceCount))
514}
5150
=== modified file '1.25.6/fake-juju.go'
--- 1.25.6/fake-juju.go 2016-06-10 17:07:27 +0000
+++ 1.25.6/fake-juju.go 2016-10-25 17:25:06 +0000
@@ -37,52 +37,51 @@
37)37)
3838
39func main() {39func main() {
40 code := 0
40 if len(os.Args) > 1 {41 if len(os.Args) > 1 {
41 code := 0
42 err := handleCommand(os.Args[1])42 err := handleCommand(os.Args[1])
43 if err != nil {43 if err != nil {
44 fmt.Println(err.Error())44 fmt.Println(err.Error())
45 code = 145 code = 1
46 }46 }
47 os.Exit(code)47 } else {
48 // This kicks off the daemon. See FakeJujuSuite below.
49 t := &testing.T{}
50 coretesting.MgoTestPackage(t)
48 }51 }
49 t := &testing.T{}52 os.Exit(code)
50 coretesting.MgoTestPackage(t)
51}
52
53type processInfo struct {
54 Username string
55 WorkDir string
56 EndpointAddr string
57 Uuid string
58 CACert string
59}53}
6054
61func handleCommand(command string) error {55func handleCommand(command string) error {
56 filenames := newFakeJujuFilenames("", "", "")
62 if command == "bootstrap" {57 if command == "bootstrap" {
63 return bootstrap()58 return bootstrap(filenames)
64 }59 }
65 if command == "api-endpoints" {60 if command == "api-endpoints" {
66 return apiEndpoints()61 return apiEndpoints(filenames)
67 }62 }
68 if command == "api-info" {63 if command == "api-info" {
69 return apiInfo()64 return apiInfo(filenames)
70 }65 }
71 if command == "destroy-environment" {66 if command == "destroy-environment" {
72 return destroyEnvironment()67 return destroyEnvironment(filenames)
73 }68 }
74 return errors.New("command not found")69 return errors.New("command not found")
75}70}
7671
77func bootstrap() error {72func bootstrap(filenames fakejujuFilenames) error {
73 if err := filenames.ensureDirsExist(); err != nil {
74 return err
75 }
78 envName, config, err := environmentNameAndConfig()76 envName, config, err := environmentNameAndConfig()
79 if err != nil {77 if err != nil {
80 return err78 return err
81 }79 }
80 password := config.AdminSecret()
81
82 command := exec.Command(os.Args[0])82 command := exec.Command(os.Args[0])
83 command.Env = os.Environ()83 command.Env = os.Environ()
84 command.Env = append(84 command.Env = append(command.Env, "ADMIN_PASSWORD="+password)
85 command.Env, "ADMIN_PASSWORD="+config.AdminSecret())
86 defaultSeries, _ := config.DefaultSeries()85 defaultSeries, _ := config.DefaultSeries()
87 command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries)86 command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries)
88 stdout, err := command.StdoutPipe()87 stdout, err := command.StdoutPipe()
@@ -90,10 +89,23 @@
90 return err89 return err
91 }90 }
92 command.Start()91 command.Start()
93 apiInfo, err := parseApiInfo(envName, stdout)92
93 result, err := parseApiInfo(stdout)
94 if err != nil {94 if err != nil {
95 return err95 return err
96 }96 }
97 // Get the API info before changing it. The new values might
98 // not work yet.
99 apiInfo := result.apiInfo()
100 // We actually want to report the API user we added in SetUpTest().
101 result.username = "admin"
102 if password != "" {
103 result.password = password
104 }
105 if err := result.apply(filenames, envName); err != nil {
106 return err
107 }
108
97 dialOpts := api.DialOpts{109 dialOpts := api.DialOpts{
98 DialAddressInterval: 50 * time.Millisecond,110 DialAddressInterval: 50 * time.Millisecond,
99 Timeout: 5 * time.Second,111 Timeout: 5 * time.Second,
@@ -123,8 +135,8 @@
123 return errors.New("invalid delta")135 return errors.New("invalid delta")
124}136}
125137
126func apiEndpoints() error {138func apiEndpoints(filenames fakejujuFilenames) error {
127 info, err := readProcessInfo()139 info, err := readProcessInfo(filenames)
128 if err != nil {140 if err != nil {
129 return err141 return err
130 }142 }
@@ -132,23 +144,22 @@
132 return nil144 return nil
133}145}
134146
135func apiInfo() error {147func apiInfo(filenames fakejujuFilenames) error {
136 info, err := readProcessInfo()148 info, err := readProcessInfo(filenames)
137 if err != nil {149 if err != nil {
138 return err150 return err
139 }151 }
140 username := strings.Replace(string(info.Username), "dummy-", "", 1)152 fmt.Printf("{\"user\": \"%s\", \"password\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", info.Username, info.Password, info.Uuid, info.EndpointAddr)
141 fmt.Printf("{\"user\": \"%s\", \"environ-uuid\": \"%s\", \"state-servers\": [\"%s\"]}\n", username, info.Uuid, info.EndpointAddr)
142 return nil153 return nil
143}154}
144155
145func destroyEnvironment() error {156func destroyEnvironment(filenames fakejujuFilenames) error {
146 info, err := readProcessInfo()157 info, err := readProcessInfo(filenames)
147 if err != nil {158 if err != nil {
148 return err159 return err
149 }160 }
150 fifoPath := filepath.Join(info.WorkDir, "fifo")161 filenames = newFakeJujuFilenames("", "", info.WorkDir)
151 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)162 fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600)
152 if err != nil {163 if err != nil {
153 return err164 return err
154 }165 }
@@ -176,93 +187,235 @@
176 return envName, config, nil187 return envName, config, nil
177}188}
178189
179func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {190// processInfo holds all the information that fake-juju uses internally.
191type processInfo struct {
192 Username string
193 Password string
194 WorkDir string
195 EndpointAddr string
196 Uuid string
197 CACert []byte
198}
199
200func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
201 infoPath := filenames.infoFile()
202 data, err := ioutil.ReadFile(infoPath)
203 if err != nil {
204 return nil, err
205 }
206 info := &processInfo{}
207 err = goyaml.Unmarshal(data, info)
208 if err != nil {
209 return nil, err
210 }
211 return info, nil
212}
213
214func (info processInfo) write(infoPath string) error {
215 data, _ := goyaml.Marshal(&info)
216 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
217 return err
218 }
219 return nil
220}
221
222// fakejujuFilenames encapsulates the paths to all the directories and
223// files that are relevant to fake-juju.
224type fakejujuFilenames struct {
225 datadir string
226 logsdir string
227}
228
229func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
230 if datadir == "" {
231 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
232 if datadir == "" {
233 if jujucfgdir == "" {
234 jujucfgdir = os.Getenv("JUJU_HOME")
235 }
236 datadir = jujucfgdir
237 }
238 }
239 if logsdir == "" {
240 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
241 if logsdir == "" {
242 logsdir = datadir
243 }
244 }
245 return fakejujuFilenames{datadir, logsdir}
246}
247
248func (fj fakejujuFilenames) ensureDirsExist() error {
249 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
250 return err
251 }
252 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
253 return err
254 }
255 return nil
256}
257
258// infoFile() returns the path to the file that fake-juju uses as
259// its persistent storage for internal data.
260func (fj fakejujuFilenames) infoFile() string {
261 return filepath.Join(fj.datadir, "fakejuju")
262}
263
264// logsFile() returns the path to the file where fake-juju writes
265// its logs. Note that the normal Juju logs are not written here.
266func (fj fakejujuFilenames) logsFile() string {
267 return filepath.Join(fj.logsdir, "fake-juju.log")
268}
269
270// fifoFile() returns the path to the FIFO file used by fake-juju.
271// The FIFO is used by the fake-juju subcommands to interact with
272// the daemon.
273func (fj fakejujuFilenames) fifoFile() string {
274 return filepath.Join(fj.datadir, "fifo")
275}
276
277// caCertFile() returns the path to the file holding the CA certificate
278// used by the Juju API server. fake-juju writes the cert there as a
279// convenience for users. It is not actually used for anything.
280func (fj fakejujuFilenames) caCertFile() string {
281 return filepath.Join(fj.datadir, "cert.ca")
282}
283
284// bootstrapResult encapsulates all significant information that came
285// from bootstrapping an environment.
286type bootstrapResult struct {
287 dummyEnvName string
288 cfgdir string
289 uuid string
290 username string
291 password string
292 addresses []string
293 caCert []byte
294}
295
296// apiInfo() composes the Juju API info corresponding to the result.
297func (br bootstrapResult) apiInfo() *api.Info {
298 return &api.Info{
299 Addrs: br.addresses,
300 Tag: names.NewLocalUserTag(br.username),
301 Password: br.password,
302 CACert: string(br.caCert),
303 EnvironTag: names.NewEnvironTag(br.uuid),
304 }
305}
306
307// fakeJujuInfo() composes, from the result, the set of information
308// that fake-juju should use internally.
309func (br bootstrapResult) fakeJujuInfo() *processInfo {
310 return &processInfo{
311 Username: br.username,
312 Password: br.password,
313 WorkDir: br.cfgdir,
314 EndpointAddr: br.addresses[0],
315 Uuid: br.uuid,
316 CACert: br.caCert,
317 }
318}
319
320// logsSymlinkFilenames() determines the source and target paths for
321// a symlink to the fake-juju logs file. Such a symlink is relevant
322// because the fake-juju daemon may not know where the log file is
323// meant to go. It defaults to putting the log file in the default Juju
324// config dir. In that case, a symlink should be created from there to
325// the user-defined Juju config dir ($JUJU_HOME).
326func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) {
327 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" || os.Getenv("FAKE_JUJU_DATA_DIR") != "" {
328 return "", ""
329 }
330
331 filenames := newFakeJujuFilenames("", "", br.cfgdir)
332 source = filenames.logsFile()
333 target = targetLogsFile
334 return source, target
335}
336
337// jenvSymlinkFilenames() determines the source and target paths for
338// a symlink to the .jenv file for the identified environment.
339func (br bootstrapResult) jenvSymlinkFilenames(jujuHome, envName string) (source, target string) {
340 if jujuHome == "" || envName == "" {
341 return "", ""
342 }
343
344 source = filepath.Join(br.cfgdir, "environments", br.dummyEnvName+".jenv")
345 target = filepath.Join(jujuHome, "environments", envName+".jenv")
346 return source, target
347}
348
349// apply() writes out the information from the bootstrap result to the
350// various files identified by the provided filenames.
351func (br bootstrapResult) apply(filenames fakejujuFilenames, envName string) error {
352 if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil {
353 return err
354 }
355
356 logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile())
357 if logsSource != "" && logsTarget != "" {
358 if err := os.Symlink(logsSource, logsTarget); err != nil {
359 return err
360 }
361 }
362
363 jenvSource, jenvTarget := br.jenvSymlinkFilenames(os.Getenv("JUJU_HOME"), envName)
364 if jenvSource != "" && jenvTarget != "" {
365 if err := os.MkdirAll(filepath.Dir(jenvTarget), 0755); err != nil {
366 return err
367 }
368 if err := os.Symlink(jenvSource, jenvTarget); err != nil {
369 return err
370 }
371 }
372
373 if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil {
374 return err
375 }
376
377 return nil
378}
379
380// See github.com/juju/juju/blob/juju/testing/conn.go.
381const dummyEnvName = "dummyenv"
382
383func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
180 buffer := bufio.NewReader(stdout)384 buffer := bufio.NewReader(stdout)
385
181 line, _, err := buffer.ReadLine()386 line, _, err := buffer.ReadLine()
182 if err != nil {387 if err != nil {
183 return nil, err388 return nil, err
184 }389 }
185 uuid := string(line)390 uuid := string(line)
186 environTag := names.NewEnvironTag(uuid)391
187 line, _, err = buffer.ReadLine()392 line, _, err = buffer.ReadLine()
188 if err != nil {393 if err != nil {
189 return nil, err394 return nil, err
190 }395 }
191 workDir := string(line)396 workDir := string(line)
397
192 store, err := configstore.NewDisk(workDir)398 store, err := configstore.NewDisk(workDir)
193 if err != nil {399 if err != nil {
194 return nil, err400 return nil, err
195 }401 }
196 info, err := store.ReadInfo("dummyenv")402 info, err := store.ReadInfo(dummyEnvName)
197 if err != nil {403 if err != nil {
198 return nil, err404 return nil, err
199 }405 }
406
200 credentials := info.APICredentials()407 credentials := info.APICredentials()
201 endpoint := info.APIEndpoint()408 endpoint := info.APIEndpoint()
202 addresses := endpoint.Addresses409 result := &bootstrapResult{
203 apiInfo := &api.Info{410 dummyEnvName: dummyEnvName,
204 Addrs: addresses,411 cfgdir: workDir,
205 Tag: names.NewLocalUserTag(credentials.User),412 uuid: uuid,
206 Password: credentials.Password,413 username: credentials.User,
207 CACert: endpoint.CACert,414 password: credentials.Password,
208 EnvironTag: environTag,415 addresses: endpoint.Addresses,
209 }416 caCert: []byte(endpoint.CACert),
210 err = writeProcessInfo(envName, &processInfo{417 }
211 Username: credentials.User,418 return result, nil
212 WorkDir: workDir,
213 EndpointAddr: addresses[0],
214 Uuid: uuid,
215 CACert: endpoint.CACert,
216 })
217 if err != nil {
218 return nil, err
219 }
220 return apiInfo, nil
221}
222
223func readProcessInfo() (*processInfo, error) {
224 infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju")
225 data, err := ioutil.ReadFile(infoPath)
226 if err != nil {
227 return nil, err
228 }
229 info := &processInfo{}
230 err = goyaml.Unmarshal(data, info)
231 if err != nil {
232 return nil, err
233 }
234 return info, nil
235}
236
237func writeProcessInfo(envName string, info *processInfo) error {
238 var err error
239 jujuHome := os.Getenv("JUJU_HOME")
240 infoPath := filepath.Join(jujuHome, "fakejuju")
241 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")
242 if logsDir == "" {
243 logsDir = jujuHome
244 }
245 logPath := filepath.Join(logsDir, "fake-juju.log")
246 caCertPath := filepath.Join(jujuHome, "cert.ca")
247 envPath := filepath.Join(jujuHome, "environments")
248 os.Mkdir(envPath, 0755)
249 jEnvPath := filepath.Join(envPath, envName+".jenv")
250 data, _ := goyaml.Marshal(info)
251 if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" {
252 err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath)
253 if err != nil {
254 return err
255 }
256 }
257 err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath)
258 if err != nil {
259 return err
260 }
261 err = ioutil.WriteFile(infoPath, data, 0644)
262 if err != nil {
263 return err
264 }
265 return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644)
266}419}
267420
268// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment421// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -302,12 +455,16 @@
302 return failuresInfo, nil455 return failuresInfo, nil
303}456}
304457
458//===================================================================
459// The fake-juju daemon (started by bootstrap) is found here. It is
460// implemented as a test suite.
461
305type FakeJujuSuite struct {462type FakeJujuSuite struct {
306 jujutesting.JujuConnSuite463 jujutesting.JujuConnSuite
307464
308 instanceCount int465 instanceCount int
309 machineStarted map[string]bool466 machineStarted map[string]bool
310 fifoPath string467 filenames fakejujuFilenames
311 logFile *os.File468 logFile *os.File
312}469}
313470
@@ -359,7 +516,6 @@
359 c.Assert(err, gc.IsNil)516 c.Assert(err, gc.IsNil)
360517
361 apiInfo := s.APIInfo(c)518 apiInfo := s.APIInfo(c)
362 //fmt.Println(apiInfo.Addrs[0])
363 jujuHome := osenv.JujuHome()519 jujuHome := osenv.JujuHome()
364 // IMPORTANT: don't remove this logging because it's used by the520 // IMPORTANT: don't remove this logging because it's used by the
365 // bootstrap command.521 // bootstrap command.
@@ -374,15 +530,11 @@
374 c.Assert(err, gc.IsNil)530 c.Assert(err, gc.IsNil)
375 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))531 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
376532
377 s.fifoPath = filepath.Join(jujuHome, "fifo")533 s.filenames = newFakeJujuFilenames("", "", jujuHome)
378 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)534 syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0)
379535
380 // Logging536 // Logging
381 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")537 logPath := s.filenames.logsFile()
382 if logsDir == "" {
383 logsDir = jujuHome
384 }
385 logPath := filepath.Join(logsDir, "fake-juju.log")
386 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)538 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
387 c.Assert(err, gc.IsNil)539 c.Assert(err, gc.IsNil)
388540
@@ -402,16 +554,30 @@
402}554}
403555
404func (s *FakeJujuSuite) TestStart(c *gc.C) {556func (s *FakeJujuSuite) TestStart(c *gc.C) {
557 fifoPath := s.filenames.fifoFile()
405 watcher := s.State.Watch()558 watcher := s.State.Watch()
406 go func() {559 go func() {
407 log.Println("Open commands FIFO", s.fifoPath)560 log.Println("Open commands FIFO", fifoPath)
408 fd, err := os.Open(s.fifoPath)561 fd, err := os.Open(fifoPath)
409 if err != nil {562 if err != nil {
410 log.Println("Failed to open commands FIFO")563 log.Println("Failed to open commands FIFO")
411 }564 }
412 c.Assert(err, gc.IsNil)565 c.Assert(err, gc.IsNil)
566 defer func() {
567 if err := fd.Close(); err != nil {
568 c.Logf("failed closing FIFO file: %s", err)
569 }
570 // Mark the controller as destroyed by renaming some files.
571 if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil {
572 c.Logf("failed renaming FIFO file: %s", err)
573 }
574 infofile := s.filenames.infoFile()
575 if err := os.Rename(infofile, infofile+".destroyed"); err != nil {
576 c.Logf("failed renaming info file: %s", err)
577 }
578 }()
413 scanner := bufio.NewScanner(fd)579 scanner := bufio.NewScanner(fd)
414 log.Println("Listen for commands on FIFO", s.fifoPath)580 log.Println("Listen for commands on FIFO", fifoPath)
415 scanner.Scan()581 scanner.Scan()
416 log.Println("Stopping fake-juju")582 log.Println("Stopping fake-juju")
417 watcher.Stop()583 watcher.Stop()
418584
=== renamed directory '2.0-beta17' => '2.0.0'
=== modified file '2.0.0/fake-juju.go'
--- 2.0-beta17/fake-juju.go 2016-09-15 19:05:50 +0000
+++ 2.0.0/fake-juju.go 2016-10-25 17:25:06 +0000
@@ -38,46 +38,45 @@
38)38)
3939
40func main() {40func main() {
41 code := 0
41 if len(os.Args) > 1 {42 if len(os.Args) > 1 {
42 code := 0
43 err := handleCommand(os.Args[1])43 err := handleCommand(os.Args[1])
44 if err != nil {44 if err != nil {
45 fmt.Println(err.Error())45 fmt.Println(err.Error())
46 code = 146 code = 1
47 }47 }
48 os.Exit(code)48 } else {
49 // This kicks off the daemon. See FakeJujuSuite below.
50 t := &testing.T{}
51 coretesting.MgoTestPackage(t)
49 }52 }
50 t := &testing.T{}53 os.Exit(code)
51 coretesting.MgoTestPackage(t)
52}
53
54type processInfo struct {
55 WorkDir string
56 EndpointAddr string
57 Uuid string
58 CACert string
59}54}
6055
61func handleCommand(command string) error {56func handleCommand(command string) error {
57 filenames := newFakeJujuFilenames("", "", "")
62 if command == "bootstrap" {58 if command == "bootstrap" {
63 return bootstrap()59 return bootstrap(filenames)
64 }60 }
65 if command == "show-controller" {61 if command == "show-controller" {
66 return apiInfo()62 return apiInfo(filenames)
67 }63 }
68 if command == "destroy-controller" {64 if command == "destroy-controller" {
69 return destroyEnvironment()65 return destroyController(filenames)
70 }66 }
71 return errors.New("command not found")67 return errors.New("command not found")
72}68}
7369
74func bootstrap() error {70func bootstrap(filenames fakejujuFilenames) error {
75 argc := len(os.Args)71 argc := len(os.Args)
76 if argc < 4 {72 if argc < 4 {
77 return errors.New(73 return errors.New(
78 "error: controller name and cloud name are required")74 "error: controller name and cloud name are required")
79 }75 }
80 envName := os.Args[argc-2]76 if err := filenames.ensureDirsExist(); err != nil {
77 return err
78 }
79 controllerName := os.Args[argc-1]
81 command := exec.Command(os.Args[0])80 command := exec.Command(os.Args[0])
82 command.Env = os.Environ()81 command.Env = os.Environ()
83 command.Env = append(82 command.Env = append(
@@ -89,10 +88,16 @@
89 return err88 return err
90 }89 }
91 command.Start()90 command.Start()
92 apiInfo, err := parseApiInfo(envName, stdout)91
92 result, err := parseApiInfo(stdout)
93 if err != nil {93 if err != nil {
94 return err94 return err
95 }95 }
96 if err := result.apply(filenames, controllerName); err != nil {
97 return err
98 }
99 apiInfo := result.apiInfo()
100
96 dialOpts := api.DialOpts{101 dialOpts := api.DialOpts{
97 DialAddressInterval: 50 * time.Millisecond,102 DialAddressInterval: 50 * time.Millisecond,
98 Timeout: 5 * time.Second,103 Timeout: 5 * time.Second,
@@ -122,8 +127,8 @@
122 return errors.New("invalid delta")127 return errors.New("invalid delta")
123}128}
124129
125func apiInfo() error {130func apiInfo(filenames fakejujuFilenames) error {
126 info, err := readProcessInfo()131 info, err := readProcessInfo(filenames)
127 if err != nil {132 if err != nil {
128 return err133 return err
129 }134 }
@@ -131,22 +136,24 @@
131 jujuHome := os.Getenv("JUJU_DATA")136 jujuHome := os.Getenv("JUJU_DATA")
132 osenv.SetJujuXDGDataHome(jujuHome)137 osenv.SetJujuXDGDataHome(jujuHome)
133 cmd := controller.NewShowControllerCommand()138 cmd := controller.NewShowControllerCommand()
134 ctx, err := coretesting.RunCommandInDir(139 if err := coretesting.InitCommand(cmd, os.Args[2:]); err != nil {
135 nil, cmd, os.Args[2:], info.WorkDir)140 return err
136 if err != nil {141 }
142 ctx := coretesting.ContextForDir(nil, info.WorkDir)
143 if err := cmd.Run(ctx); err != nil {
137 return err144 return err
138 }145 }
139 fmt.Print(ctx.Stdout)146 fmt.Print(ctx.Stdout)
140 return nil147 return nil
141}148}
142149
143func destroyEnvironment() error {150func destroyController(filenames fakejujuFilenames) error {
144 info, err := readProcessInfo()151 info, err := readProcessInfo(filenames)
145 if err != nil {152 if err != nil {
146 return err153 return err
147 }154 }
148 fifoPath := filepath.Join(info.WorkDir, "fifo")155 filenames = newFakeJujuFilenames("", "", info.WorkDir)
149 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)156 fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600)
150 if err != nil {157 if err != nil {
151 return err158 return err
152 }159 }
@@ -158,13 +165,214 @@
158 return nil165 return nil
159}166}
160167
161func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {168// processInfo holds all the information that fake-juju uses internally.
169type processInfo struct {
170 WorkDir string
171 EndpointAddr string
172 Uuid string
173 CACert []byte
174}
175
176func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
177 infoPath := filenames.infoFile()
178 data, err := ioutil.ReadFile(infoPath)
179 if err != nil {
180 return nil, err
181 }
182 info := &processInfo{}
183 err = goyaml.Unmarshal(data, info)
184 if err != nil {
185 return nil, err
186 }
187 return info, nil
188}
189
190func (info processInfo) write(infoPath string) error {
191 data, _ := goyaml.Marshal(&info)
192 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
193 return err
194 }
195 return nil
196}
197
198// fakejujuFilenames encapsulates the paths to all the directories and
199// files that are relevant to fake-juju.
200type fakejujuFilenames struct {
201 datadir string
202 logsdir string
203}
204
205func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
206 if datadir == "" {
207 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
208 if datadir == "" {
209 if jujucfgdir == "" {
210 jujucfgdir = os.Getenv("JUJU_DATA")
211 }
212 datadir = jujucfgdir
213 }
214 }
215 if logsdir == "" {
216 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
217 if logsdir == "" {
218 logsdir = datadir
219 }
220 }
221 return fakejujuFilenames{datadir, logsdir}
222}
223
224func (fj fakejujuFilenames) ensureDirsExist() error {
225 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
226 return err
227 }
228 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
229 return err
230 }
231 return nil
232}
233
234// infoFile() returns the path to the file that fake-juju uses as
235// its persistent storage for internal data.
236func (fj fakejujuFilenames) infoFile() string {
237 return filepath.Join(fj.datadir, "fakejuju")
238}
239
240// logsFile() returns the path to the file where fake-juju writes
241// its logs. Note that the normal Juju logs are not written here.
242func (fj fakejujuFilenames) logsFile() string {
243 return filepath.Join(fj.logsdir, "fake-juju.log")
244}
245
246// fifoFile() returns the path to the FIFO file used by fake-juju.
247// The FIFO is used by the fake-juju subcommands to interact with
248// the daemon.
249func (fj fakejujuFilenames) fifoFile() string {
250 return filepath.Join(fj.datadir, "fifo")
251}
252
253// caCertFile() returns the path to the file holding the CA certificate
254// used by the Juju API server. fake-juju writes the cert there as a
255// convenience for users. It is not actually used for anything.
256func (fj fakejujuFilenames) caCertFile() string {
257 return filepath.Join(fj.datadir, "cert.ca")
258}
259
260// bootstrapResult encapsulates all significant information that came
261// from bootstrapping a controller.
262type bootstrapResult struct {
263 dummyControllerName string
264 cfgdir string
265 uuid string
266 username string
267 password string
268 addresses []string
269 caCert []byte
270}
271
272// apiInfo() composes the Juju API info corresponding to the result.
273func (br bootstrapResult) apiInfo() *api.Info {
274 return &api.Info{
275 Addrs: br.addresses,
276 Tag: names.NewUserTag(br.username),
277 Password: br.password,
278 CACert: string(br.caCert),
279 ModelTag: names.NewModelTag(br.uuid),
280 }
281}
282
283// fakeJujuInfo() composes, from the result, the set of information
284// that fake-juju should use internally.
285func (br bootstrapResult) fakeJujuInfo() *processInfo {
286 return &processInfo{
287 WorkDir: br.cfgdir,
288 EndpointAddr: br.addresses[0],
289 Uuid: br.uuid,
290 CACert: br.caCert,
291 }
292}
293
294// logsSymlinkFilenames() determines the source and target paths for
295// a symlink to the fake-juju logs file. Such a symlink is relevant
296// because the fake-juju daemon may not know where the log file is
297// meant to go. It defaults to putting the log file in the default Juju
298// config dir. In that case, a symlink should be created from there to
299// the user-defined Juju config dir ($JUJU_DATA).
300func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) {
301 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" {
302 return "", ""
303 }
304
305 filenames := newFakeJujuFilenames("", "", br.cfgdir)
306 source = filenames.logsFile()
307 target = targetLogsFile
308 return source, target
309}
310
311// apply() writes out the information from the bootstrap result to the
312// various files identified by the provided filenames.
313func (br bootstrapResult) apply(filenames fakejujuFilenames, controllerName string) error {
314 if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil {
315 return err
316 }
317
318 logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile())
319 if logsSource != "" && logsTarget != "" {
320 if err := os.Symlink(logsSource, logsTarget); err != nil {
321 return err
322 }
323 }
324
325 if err := br.copyConfig(os.Getenv("JUJU_DATA"), controllerName); err != nil {
326 return err
327 }
328
329 if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil {
330 return err
331 }
332
333 return nil
334}
335
336func (br bootstrapResult) copyConfig(targetCfgDir, controllerName string) error {
337 for _, name := range []string{"controllers.yaml", "models.yaml", "accounts.yaml"} {
338 source := filepath.Join(br.cfgdir, name)
339 target := filepath.Join(targetCfgDir, name)
340
341 input, err := ioutil.ReadFile(source)
342 if err != nil {
343 return err
344 }
345 // Generated configuration by test fixtures has the controller name
346 // hard-coded to "kontroll". A simple replace should fix this for
347 // clients using this config and expecting a specific controller
348 // name.
349 output := strings.Replace(string(input), dummyControllerName, controllerName, -1)
350 err = ioutil.WriteFile(target, []byte(output), 0644)
351 if err != nil {
352 return err
353 }
354 }
355
356 current := filepath.Join(targetCfgDir, "current-controller")
357 if err := ioutil.WriteFile(current, []byte(controllerName), 0644); err != nil {
358 return err
359 }
360
361 return nil
362}
363
364// See github.com/juju/juju/blob/juju/testing/conn.go.
365const dummyControllerName = "kontroll"
366
367func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
162 buffer := bufio.NewReader(stdout)368 buffer := bufio.NewReader(stdout)
369
163 line, _, err := buffer.ReadLine()370 line, _, err := buffer.ReadLine()
164 if err != nil {371 if err != nil {
165 return nil, err372 return nil, err
166 }373 }
167 uuid := string(line)374 uuid := string(line)
375
168 line, _, err = buffer.ReadLine()376 line, _, err = buffer.ReadLine()
169 if err != nil {377 if err != nil {
170 return nil, err378 return nil, err
@@ -175,8 +383,8 @@
175 store := jujuclient.NewFileClientStore()383 store := jujuclient.NewFileClientStore()
176 // hard-coded value in juju testing384 // hard-coded value in juju testing
177 // This will be replaced in JUJU_DATA copy of the juju client config.385 // This will be replaced in JUJU_DATA copy of the juju client config.
178 currentController := "kontroll"386 currentController := dummyControllerName
179 one, err := store.ControllerByName("kontroll")387 one, err := store.ControllerByName(currentController)
180 if err != nil {388 if err != nil {
181 return nil, err389 return nil, err
182 }390 }
@@ -185,108 +393,17 @@
185 if err != nil {393 if err != nil {
186 return nil, err394 return nil, err
187 }395 }
188 apiInfo := &api.Info{396
189 Addrs: one.APIEndpoints,397 result := &bootstrapResult{
190 Tag: names.NewUserTag(accountDetails.User),398 dummyControllerName: dummyControllerName,
191 Password: accountDetails.Password,399 cfgdir: workDir,
192 CACert: one.CACert,400 uuid: uuid,
193 ModelTag: names.NewModelTag(uuid),401 username: accountDetails.User,
194 }402 password: accountDetails.Password,
195403 addresses: one.APIEndpoints,
196 err = writeProcessInfo(envName, &processInfo{404 caCert: []byte(one.CACert),
197 WorkDir: workDir,405 }
198 EndpointAddr: one.APIEndpoints[0],406 return result, nil
199 Uuid: uuid,
200 CACert: one.CACert,
201 })
202 if err != nil {
203 return nil, err
204 }
205 return apiInfo, nil
206}
207
208func readProcessInfo() (*processInfo, error) {
209 infoPath := filepath.Join(os.Getenv("JUJU_DATA"), "fakejuju")
210 data, err := ioutil.ReadFile(infoPath)
211 if err != nil {
212 return nil, err
213 }
214 info := &processInfo{}
215 err = goyaml.Unmarshal(data, info)
216 if err != nil {
217 return nil, err
218 }
219 return info, nil
220}
221
222func writeProcessInfo(envName string, info *processInfo) error {
223 var err error
224 jujuHome := os.Getenv("JUJU_DATA")
225 infoPath := filepath.Join(jujuHome, "fakejuju")
226 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")
227 if logsDir == "" {
228 logsDir = jujuHome
229 }
230 logPath := filepath.Join(logsDir, "fake-juju.log")
231 caCertPath := filepath.Join(jujuHome, "cert.ca")
232 data, _ := goyaml.Marshal(info)
233 if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" {
234 err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath)
235 if err != nil {
236 return err
237 }
238 }
239
240 err = copyClientConfig(
241 filepath.Join(info.WorkDir, "controllers.yaml"),
242 filepath.Join(jujuHome, "controllers.yaml"),
243 envName)
244 if err != nil {
245 return err
246 }
247 err = copyClientConfig(
248 filepath.Join(info.WorkDir, "models.yaml"),
249 filepath.Join(jujuHome, "models.yaml"),
250 envName)
251 if err != nil {
252 return err
253 }
254 err = copyClientConfig(
255 filepath.Join(info.WorkDir, "accounts.yaml"),
256 filepath.Join(jujuHome, "accounts.yaml"),
257 envName)
258 if err != nil {
259 return err
260 }
261 err = ioutil.WriteFile(
262 filepath.Join(jujuHome, "current-controller"),
263 []byte(envName), 0644)
264 if err != nil {
265 return err
266 }
267
268 err = ioutil.WriteFile(infoPath, data, 0644)
269 if err != nil {
270 return err
271 }
272 return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644)
273}
274
275func copyClientConfig(src string, dst string, envName string) error {
276 input, err := ioutil.ReadFile(src)
277 if err != nil {
278 return err
279 }
280 // Generated configuration by test fixtures has the controller name
281 // hard-coded to "kontroll". A simple replace should fix this for
282 // clients using this config and expecting a specific controller
283 // name.
284 output := strings.Replace(string(input), "kontroll", envName, -1)
285 err = ioutil.WriteFile(dst, []byte(output), 0644)
286 if err != nil {
287 return err
288 }
289 return nil
290}407}
291408
292// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment409// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -326,12 +443,16 @@
326 return failuresInfo, nil443 return failuresInfo, nil
327}444}
328445
446//===================================================================
447// The fake-juju daemon (started by bootstrap) is found here. It is
448// implemented as a test suite.
449
329type FakeJujuSuite struct {450type FakeJujuSuite struct {
330 jujutesting.JujuConnSuite451 jujutesting.JujuConnSuite
331452
332 instanceCount int453 instanceCount int
333 machineStarted map[string]bool454 machineStarted map[string]bool
334 fifoPath string455 filenames fakejujuFilenames
335 logFile *os.File456 logFile *os.File
336}457}
337458
@@ -371,7 +492,7 @@
371 c.Assert(stateServer.SetProviderAddresses(address), gc.IsNil)492 c.Assert(stateServer.SetProviderAddresses(address), gc.IsNil)
372 now := time.Now()493 now := time.Now()
373 sInfo := states.StatusInfo{494 sInfo := states.StatusInfo{
374 Status: states.StatusStarted,495 Status: states.Started,
375 Message: "",496 Message: "",
376 Since: &now,497 Since: &now,
377 }498 }
@@ -398,20 +519,16 @@
398 c.Assert(err, gc.IsNil)519 c.Assert(err, gc.IsNil)
399 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))520 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
400521
401 s.fifoPath = filepath.Join(jujuHome, "fifo")522 s.filenames = newFakeJujuFilenames("", "", jujuHome)
402 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)523 syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0)
403524
404 // Logging525 // Logging
405 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")526 logPath := s.filenames.logsFile()
406 if logsDir == "" {
407 logsDir = jujuHome
408 }
409 logPath := filepath.Join(logsDir, "fake-juju.log")
410 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)527 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
411 c.Assert(err, gc.IsNil)528 c.Assert(err, gc.IsNil)
412529
413 log.SetOutput(s.logFile)530 log.SetOutput(s.logFile)
414 log.Println("Started fake-juju at", jujuHome)531 log.Println("Started fake-juju at ", jujuHome)
415532
416}533}
417534
@@ -423,16 +540,30 @@
423}540}
424541
425func (s *FakeJujuSuite) TestStart(c *gc.C) {542func (s *FakeJujuSuite) TestStart(c *gc.C) {
543 fifoPath := s.filenames.fifoFile()
426 watcher := s.State.Watch()544 watcher := s.State.Watch()
427 go func() {545 go func() {
428 log.Println("Open commands FIFO", s.fifoPath)546 log.Println("Open commands FIFO", fifoPath)
429 fd, err := os.Open(s.fifoPath)547 fd, err := os.Open(fifoPath)
430 if err != nil {548 if err != nil {
431 log.Println("Failed to open commands FIFO")549 log.Println("Failed to open commands FIFO")
432 }550 }
433 c.Assert(err, gc.IsNil)551 c.Assert(err, gc.IsNil)
552 defer func() {
553 if err := fd.Close(); err != nil {
554 c.Logf("failed closing FIFO file: %s", err)
555 }
556 // Mark the controller as destroyed by renaming some files.
557 if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil {
558 c.Logf("failed renaming FIFO file: %s", err)
559 }
560 infofile := s.filenames.infoFile()
561 if err := os.Rename(infofile, infofile+".destroyed"); err != nil {
562 c.Logf("failed renaming info file: %s", err)
563 }
564 }()
434 scanner := bufio.NewScanner(fd)565 scanner := bufio.NewScanner(fd)
435 log.Println("Listen for commands on FIFO", s.fifoPath)566 log.Println("Listen for commands on FIFO", fifoPath)
436 scanner.Scan()567 scanner.Scan()
437 log.Println("Stopping fake-juju")568 log.Println("Stopping fake-juju")
438 watcher.Stop()569 watcher.Stop()
@@ -495,12 +626,12 @@
495 }626 }
496 status, _ := machine.Status()627 status, _ := machine.Status()
497 log.Println("Machine has status:", string(status.Status), status.Message)628 log.Println("Machine has status:", string(status.Status), status.Message)
498 if status.Status == states.StatusPending {629 if status.Status == states.Pending {
499 if err = s.startMachine(machine); err != nil {630 if err = s.startMachine(machine); err != nil {
500 log.Println("Got error with startMachine:", err)631 log.Println("Got error with startMachine:", err)
501 return err632 return err
502 }633 }
503 } else if status.Status == states.StatusStarted {634 } else if status.Status == states.Started {
504 log.Println("Starting units on machine", id)635 log.Println("Starting units on machine", id)
505 if _, ok := s.machineStarted[id]; !ok {636 if _, ok := s.machineStarted[id]; !ok {
506 s.machineStarted[id] = true637 s.machineStarted[id] = true
@@ -531,19 +662,19 @@
531 return err662 return err
532 }663 }
533 machineStatus, _ := machine.Status()664 machineStatus, _ := machine.Status()
534 if machineStatus.Status != states.StatusStarted {665 if machineStatus.Status != states.Started {
535 return nil666 return nil
536 }667 }
537 status, _ := unit.Status()668 status, _ := unit.Status()
538 log.Println("Unit has status", string(status.Status), status.Message)669 log.Println("Unit has status", string(status.Status), status.Message)
539 if status.Status != states.StatusActive && status.Status != states.StatusError {670 if status.Status != states.Active && status.Status != states.Error {
540 log.Println("Start unit", id)671 log.Println("Start unit", id)
541 err = s.startUnit(unit)672 err = s.startUnit(unit)
542 if err != nil {673 if err != nil {
543 log.Println("Got error changing unit status", id, err)674 log.Println("Got error changing unit status", id, err)
544 return err675 return err
545 }676 }
546 } else if status.Status != states.StatusError {677 } else if status.Status != states.Error {
547 failuresInfo, err := readFailuresInfo()678 failuresInfo, err := readFailuresInfo()
548 if err != nil {679 if err != nil {
549 return err680 return err
@@ -554,7 +685,7 @@
554 log.Println("Got error checking agent status", id, err)685 log.Println("Got error checking agent status", id, err)
555 return err686 return err
556 }687 }
557 if agentStatus.Status != states.StatusError {688 if agentStatus.Status != states.Error {
558 log.Println("Error unit", id)689 log.Println("Error unit", id)
559 err = s.errorUnit(unit)690 err = s.errorUnit(unit)
560 if err != nil {691 if err != nil {
@@ -571,7 +702,7 @@
571 time.Sleep(500 * time.Millisecond)702 time.Sleep(500 * time.Millisecond)
572 now := time.Now()703 now := time.Now()
573 sInfo := states.StatusInfo{704 sInfo := states.StatusInfo{
574 Status: states.StatusStarted,705 Status: states.Started,
575 Message: "",706 Message: "",
576 Since: &now,707 Since: &now,
577 }708 }
@@ -604,7 +735,7 @@
604 time.Sleep(500 * time.Millisecond)735 time.Sleep(500 * time.Millisecond)
605 now := time.Now()736 now := time.Now()
606 sInfo := states.StatusInfo{737 sInfo := states.StatusInfo{
607 Status: states.StatusError,738 Status: states.Error,
608 Message: "machine errored",739 Message: "machine errored",
609 Since: &now,740 Since: &now,
610 }741 }
@@ -623,7 +754,7 @@
623 return nil754 return nil
624 for _, unit := range units {755 for _, unit := range units {
625 unitStatus, _ := unit.Status()756 unitStatus, _ := unit.Status()
626 if unitStatus.Status != states.StatusActive {757 if unitStatus.Status != states.Active {
627 if err = s.startUnit(unit); err != nil {758 if err = s.startUnit(unit); err != nil {
628 return err759 return err
629 }760 }
@@ -635,7 +766,7 @@
635func (s *FakeJujuSuite) startUnit(unit *state.Unit) error {766func (s *FakeJujuSuite) startUnit(unit *state.Unit) error {
636 now := time.Now()767 now := time.Now()
637 sInfo := states.StatusInfo{768 sInfo := states.StatusInfo{
638 Status: states.StatusStarted,769 Status: states.Started,
639 Message: "",770 Message: "",
640 Since: &now,771 Since: &now,
641 }772 }
@@ -653,7 +784,7 @@
653 return err784 return err
654 }785 }
655 idleInfo := states.StatusInfo{786 idleInfo := states.StatusInfo{
656 Status: states.StatusIdle,787 Status: states.Idle,
657 Message: "",788 Message: "",
658 Since: &now,789 Since: &now,
659 }790 }
@@ -668,7 +799,7 @@
668 log.Println("Erroring unit", unit.Name())799 log.Println("Erroring unit", unit.Name())
669 now := time.Now()800 now := time.Now()
670 sInfo := states.StatusInfo{801 sInfo := states.StatusInfo{
671 Status: states.StatusIdle,802 Status: states.Idle,
672 Message: "unit errored",803 Message: "unit errored",
673 Since: &now,804 Since: &now,
674 }805 }
675806
=== modified file 'Makefile'
--- Makefile 2016-09-20 18:26:47 +0000
+++ Makefile 2016-10-25 17:25:06 +0000
@@ -11,7 +11,7 @@
11INSTALLDIR = $(DESTDIR)/usr/bin11INSTALLDIR = $(DESTDIR)/usr/bin
12INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION)12INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION)
1313
14$(JUJU_VERSION)/$(JUJU_VERSION):14$(JUJU_VERSION)/$(JUJU_VERSION): $(JUJU_VERSION)/fake-juju.go
15 case $(JUJU_VERSION) in \15 case $(JUJU_VERSION) in \
16 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\16 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\
17 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\17 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\
@@ -59,8 +59,8 @@
59else ###########################################59else ###########################################
60# for all versions60# for all versions
6161
62JUJU1_VERSIONS = 1.24.7 1.25.662JUJU1_VERSIONS = 1.25.6
63JUJU2_VERSIONS = 2.0-beta1763JUJU2_VERSIONS = 2.0.0
64VERSIONS = $(JUJU1_VERSIONS) $(JUJU2_VERSIONS)64VERSIONS = $(JUJU1_VERSIONS) $(JUJU2_VERSIONS)
65BUILT_VERSIONS = $(foreach version,$(VERSIONS),$(version)/$(version))65BUILT_VERSIONS = $(foreach version,$(VERSIONS),$(version)/$(version))
6666
6767
=== removed file 'patches/juju-core_1.24.7.patch'
--- patches/juju-core_1.24.7.patch 2016-03-18 11:10:52 +0000
+++ patches/juju-core_1.24.7.patch 1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
1--- 1.24.7/src/github.com/juju/juju/testcharms/charm.go.orig 2015-06-24 12:02:02.746416146 +0200
2+++ 1.24.7/src/github.com/juju/juju/testcharms/charm.go 2015-06-24 12:03:49.810418650 +0200
3@@ -10,4 +10,6 @@
4 )
5
6 // Repo provides access to the test charm repository.
7-var Repo = testing.NewRepo("charm-repo", "quantal")
8+// XXX fake-juju: avoid crashing because the charm-repo dir is not there
9+//var Repo = testing.NewRepo("charm-repo", "quantal")
10+var Repo = &testing.Repo{}
11
12--- 1.24.7/src/github.com/juju/juju/provider/dummy/environs.go.orig 2015-07-06 15:01:14.200568258 +0200
13+++ 1.24.7/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +0200
14@@ -642,9 +642,9 @@
15
16 // PrecheckInstance is specified in the state.Prechecker interface.
17 func (*environ) PrecheckInstance(series string, cons constraints.Value, placement string) error {
18- if placement != "" && placement != "valid" {
19- return fmt.Errorf("%s placement is invalid", placement)
20- }
21+// if placement != "" && placement != "valid" {
22+// return fmt.Errorf("%s placement is invalid", placement)
23+// }
24 return nil
25 }
26
27--- 1.24.7/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +0000
28+++ 1.24.7/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +0000
29@@ -52,7 +52,7 @@
30 }
31
32 func mustNewCA() (string, string) {
33- cert.KeyBits = 512
34+ cert.KeyBits = 1024
35 caCert, caKey, err := cert.NewCA("juju testing", time.Now().AddDate(10, 0, 0))
36 if err != nil {
37 panic(err)
38@@ -61,7 +61,7 @@
39 }
40
41 func mustNewServer() (string, string) {
42- cert.KeyBits = 512
43+ cert.KeyBits = 1024
44 var hostnames []string
45 srvCert, srvKey, err := cert.NewServer(CACert, CAKey, time.Now().AddDate(10, 0, 0), hostnames)
46 if err != nil {
47
480
=== renamed file 'patches/juju-core_2.0-beta17.patch' => 'patches/juju-core_2.0.0.patch'
--- patches/juju-core_2.0-beta17.patch 2016-09-01 22:03:22 +0000
+++ patches/juju-core_2.0.0.patch 2016-10-25 17:25:06 +0000
@@ -1,5 +1,5 @@
1--- 2.0-beta17/src/github.com/juju/juju/testcharms/charm.go 2016-03-10 13:45:57.000000000 +01001--- 2.0.0/src/github.com/juju/juju/testcharms/charm.go 2016-03-10 13:45:57.000000000 +0100
2+++ 2.0-beta17/src/github.com/juju/juju/testcharms/charm.go 2016-03-21 10:46:24.312966629 +01002+++ 2.0.0/src/github.com/juju/juju/testcharms/charm.go 2016-03-21 10:46:24.312966629 +0100
3@@ -17,7 +17,9 @@3@@ -17,7 +17,9 @@
4 )4 )
5 5
@@ -11,8 +11,8 @@
11 11
12 // UploadCharm uploads a charm using the given charm store client, and returns12 // UploadCharm uploads a charm using the given charm store client, and returns
13 // the resulting charm URL and charm.13 // the resulting charm URL and charm.
14--- 2.0-beta17/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:01:14.200568258 +020014--- 2.0.0/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:01:14.200568258 +0200
15+++ 2.0-beta17/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +020015+++ 2.0.0/src/github.com/juju/juju/provider/dummy/environs.go 2015-07-06 15:18:32.648549661 +0200
16@@ -633,9 +633,9 @@16@@ -633,9 +633,9 @@
17 17
18 // PrecheckInstance is specified in the state.Prechecker interface.18 // PrecheckInstance is specified in the state.Prechecker interface.
@@ -26,8 +26,8 @@
26 return nil26 return nil
27 }27 }
28 28
29--- 2.0-beta17/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +000029--- 2.0.0/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:25:34 +0000
30+++ 2.0-beta17/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +000030+++ 2.0.0/src/github.com/juju/juju/testing/cert.go 2016-03-18 09:26:04 +0000
31@@ -52,7 +52,7 @@31@@ -52,7 +52,7 @@
32 }32 }
33 33
3434
=== modified file 'python/Makefile'
--- python/Makefile 2016-10-06 21:44:31 +0000
+++ python/Makefile 2016-10-25 17:25:06 +0000
@@ -6,4 +6,4 @@
66
7.PHONY: install-dev7.PHONY: install-dev
8install-dev:8install-dev:
9 ln -s $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju9 ln -snv $(shell pwd)/fakejuju /usr/local/lib/python2.7/dist-packages/fakejuju
1010
=== modified file 'python/fakejuju/fakejuju.py'
--- python/fakejuju/fakejuju.py 2016-10-17 15:54:59 +0000
+++ python/fakejuju/fakejuju.py 2016-10-25 17:25:06 +0000
@@ -2,6 +2,7 @@
22
3import os.path3import os.path
44
5import txjuju
5import txjuju.cli6import txjuju.cli
67
7from .failures import Failures8from .failures import Failures
@@ -23,15 +24,17 @@
23 return os.path.join(bindir, filename)24 return os.path.join(bindir, filename)
2425
2526
26def set_envvars(envvars, failures_filename=None, logsdir=None):27def set_envvars(envvars, datadir=None, failures_filename=None, logsdir=None):
27 """Return the environment variables with which to run fake-juju.28 """Return the environment variables with which to run fake-juju.
2829
29 @param envvars: The env dict to update.30 @param envvars: The env dict to update.
31 @param datadir: The fake-juju data directory.
30 @param failures_filename: The path to the failures file that32 @param failures_filename: The path to the failures file that
31 fake-juju will use.33 fake-juju will use.
32 @params logsdir: The path to the directory where fake-juju will34 @params logsdir: The path to the directory where fake-juju will
33 write its log files.35 write its log files.
34 """36 """
37 envvars["FAKE_JUJU_DATA_DIR"] = datadir or ""
35 envvars["FAKE_JUJU_FAILURES"] = failures_filename or ""38 envvars["FAKE_JUJU_FAILURES"] = failures_filename or ""
36 envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or ""39 envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or ""
3740
@@ -40,46 +43,47 @@
40 """The fundamental details for fake-juju."""43 """The fundamental details for fake-juju."""
4144
42 @classmethod45 @classmethod
43 def from_version(cls, version, cfgdir,46 def from_version(cls, version, datadir,
44 logsdir=None, failuresdir=None, bindir=None):47 logsdir=None, failuresdir=None, bindir=None):
45 """Return a new instance given the provided information.48 """Return a new instance given the provided information.
4649
47 @param version: The Juju version to fake.50 @param version: The Juju version to fake.
48 @param cfgdir: The "juju home" directory to use.51 @param datadir: The directory in which to store files specific
52 to fake-juju.
49 @param logsdir: The directory where logs will be written.53 @param logsdir: The directory where logs will be written.
50 This defaults to cfgdir.54 This defaults to datadir.
51 @params failuresdir: The directory where failure injection55 @params failuresdir: The directory where failure injection
52 is managed.56 is managed.
53 @param bindir: The directory containing the fake-juju binary.57 @param bindir: The directory containing the fake-juju binary.
54 This defaults to /usr/bin.58 This defaults to /usr/bin.
55 """59 """
56 if logsdir is None:
57 logsdir = cfgdir
58 if failuresdir is None:60 if failuresdir is None:
59 failuresdir = cfgdir61 failuresdir = datadir
60 filename = get_filename(version, bindir=bindir)62 filename = get_filename(version, bindir=bindir)
61 failures = Failures(failuresdir)63 failures = Failures(failuresdir)
62 return cls(filename, version, cfgdir, logsdir, failures)64 return cls(filename, version, datadir, logsdir, failures)
6365
64 def __init__(self, filename, version, cfgdir, logsdir=None, failures=None):66 def __init__(self, filename, version, datadir,
67 logsdir=None, failures=None):
65 """68 """
66 @param filename: The path to the fake-juju binary.69 @param filename: The path to the fake-juju binary.
67 @param version: The Juju version to fake.70 @param version: The Juju version to fake.
68 @param cfgdir: The "juju home" directory to use.71 @param datadir: The directory in which to store files specific
72 to fake-juju.
69 @param logsdir: The directory where logs will be written.73 @param logsdir: The directory where logs will be written.
70 This defaults to cfgdir.74 This defaults to datadir.
71 @param failures: The set of fake-juju failures to use.75 @param failures: The set of fake-juju failures to use.
72 """76 """
73 logsdir = logsdir if logsdir is not None else cfgdir77 logsdir = logsdir if logsdir is not None else datadir
74 if failures is None and cfgdir:78 if failures is None and datadir:
75 failures = Failures(cfgdir)79 failures = Failures(datadir)
7680
77 if not filename:81 if not filename:
78 raise ValueError("missing filename")82 raise ValueError("missing filename")
79 if not version:83 if not version:
80 raise ValueError("missing version")84 raise ValueError("missing version")
81 if not cfgdir:85 if not datadir:
82 raise ValueError("missing cfgdir")86 raise ValueError("missing datadir")
83 if not logsdir:87 if not logsdir:
84 raise ValueError("missing logsdir")88 raise ValueError("missing logsdir")
85 if failures is None:89 if failures is None:
@@ -87,7 +91,7 @@
8791
88 self.filename = filename92 self.filename = filename
89 self.version = version93 self.version = version
90 self.cfgdir = cfgdir94 self.datadir = datadir
91 self.logsdir = logsdir95 self.logsdir = logsdir
92 self.failures = failures96 self.failures = failures
9397
@@ -99,19 +103,19 @@
99 @property103 @property
100 def infofile(self):104 def infofile(self):
101 """The path to fake-juju's data cache."""105 """The path to fake-juju's data cache."""
102 return os.path.join(self.cfgdir, "fakejuju")106 return os.path.join(self.datadir, "fakejuju")
103107
104 @property108 @property
105 def fifo(self):109 def fifo(self):
106 """The path to the fifo file that triggers shutdown."""110 """The path to the fifo file that triggers shutdown."""
107 return os.path.join(self.cfgdir, "fifo")111 return os.path.join(self.datadir, "fifo")
108112
109 @property113 @property
110 def cacertfile(self):114 def cacertfile(self):
111 """The path to the API server's certificate."""115 """The path to the API server's certificate."""
112 return os.path.join(self.cfgdir, "cert.ca")116 return os.path.join(self.datadir, "cert.ca")
113117
114 def cli(self, envvars=None):118 def cli(self, cfgdir, envvars=None):
115 """Return the txjuju.cli.CLI for this fake-juju.119 """Return the txjuju.cli.CLI for this fake-juju.
116120
117 Currently fake-juju supports only the following juju subcommands:121 Currently fake-juju supports only the following juju subcommands:
@@ -123,10 +127,27 @@
123 Note that passwords are always omited, even if requested.127 Note that passwords are always omited, even if requested.
124 * api-endpoints128 * api-endpoints
125 * destroy-environment129 * destroy-environment
130
131 Note that fake-juju ignores local config files.
126 """132 """
127 if envvars is None:133 if envvars is None:
128 envvars = os.environ134 envvars = os.environ
129 envvars = dict(envvars)135 envvars = dict(envvars)
130 set_envvars(envvars, self.failures._filename, self.logsdir)136 set_envvars(
137 envvars, self.datadir, self.failures._filename, self.logsdir)
131 return txjuju.cli.CLI.from_version(138 return txjuju.cli.CLI.from_version(
132 self.filename, self.version, self.cfgdir, envvars)139 self.filename, self.version, cfgdir, envvars)
140
141 def bootstrap(self, name, cfgdir, admin_secret=None):
142 """Return the CLI and APIInfo after bootstrapping from scratch."""
143 from . import get_bootstrap_spec
144 spec = get_bootstrap_spec(name, admin_secret)
145 cfgfile = txjuju.prepare_for_bootstrap(spec, self.version, cfgdir)
146 cli = self.cli(cfgdir)
147 cli.bootstrap(spec, cfgfile=cfgfile)
148 api_info = cli.api_info(spec.name)
149 return cli, api_info
150
151 def is_bootstrapped(self):
152 """Return True if a fake-juju controller is running."""
153 return os.path.exists(self.fifo)
133154
=== modified file 'python/fakejuju/testing.py'
--- python/fakejuju/testing.py 2016-10-06 22:51:41 +0000
+++ python/fakejuju/testing.py 2016-10-25 17:25:06 +0000
@@ -1,6 +1,5 @@
1# Copyright 2016 Canonical Limited. All rights reserved.1# Copyright 2016 Canonical Limited. All rights reserved.
22
3import txjuju
4from fixtures import Fixture, TempDir3from fixtures import Fixture, TempDir
5from testtools.content import content_from_file4from testtools.content import content_from_file
65
@@ -8,7 +7,7 @@
87
98
10JUJU1_VER = "1.25.6"9JUJU1_VER = "1.25.6"
11JUJU2_VER = "2.0-beta17"10JUJU2_VER = "2.0.0"
12JUJU_VER = JUJU1_VER11JUJU_VER = JUJU1_VER
1312
1413
@@ -40,29 +39,16 @@
40 def setUp(self):39 def setUp(self):
41 super(FakeJujuFixture, self).setUp()40 super(FakeJujuFixture, self).setUp()
42 self._juju_home = self.useFixture(TempDir())41 self._juju_home = self.useFixture(TempDir())
43 self._juju = fakejuju.FakeJuju.make(42 self.fakejuju = fakejuju.FakeJuju.from_version(
44 self._juju_home.path, self._version, self._logs_dir)43 self._version, self._juju_home.path, self._logs_dir)
4544
46 if not self._logs_dir:45 if not self._logs_dir:
47 # Attach logs as testtools details.46 # Attach logs as testtools details.
48 self.addDetail("log-file", content_from_file(self._juju.logfile))47 self.addDetail("log-file", content_from_file(self._juju.logfile))
4948
50 spec = fakejuju.get_bootstrap_spec(self._controller, self._password)49 self._juju, self.all_api_info = self.fakejuju.bootstrap(
51 cfgfile = txjuju.prepare_for_bootstrap(50 self._controller, self._password)
52 spec, self._version, self._juju_home)
53 cli = self._juju.cli()
54 cli.bootstrap(spec, cfgfile=cfgfile)
55 api_info = cli.api_info(spec.name)
56 if self._version.startswith("1."):
57 # fake-juju doesn't give us the password, so we have to
58 # set it here.
59 api_info = api_info._replace(password=self._password)
60 self.api_info = api_info
6151
62 def cleanUp(self):52 def cleanUp(self):
63 self._juju.destroy_controller(self._controller)53 self._juju.destroy_controller(self._controller)
64 super(FakeJujuFixture, self).cleanUp()54 super(FakeJujuFixture, self).cleanUp()
65
66 def add_failure(self, entity):
67 """Make the given entity fail with an error status."""
68 self._juju.failures.fail_entity(entity)
6955
=== modified file 'python/fakejuju/tests/test_fakejuju.py'
--- python/fakejuju/tests/test_fakejuju.py 2016-10-17 15:36:15 +0000
+++ python/fakejuju/tests/test_fakejuju.py 2016-10-25 17:25:06 +0000
@@ -1,10 +1,16 @@
1# Copyright 2016 Canonical Limited. All rights reserved.1# Copyright 2016 Canonical Limited. All rights reserved.
22
3from contextlib import contextmanager
4import json
3import os5import os
6import shutil
7import tempfile
4import unittest8import unittest
59
6from txjuju import _juju1, _juju210from txjuju import _juju1, _juju2
7from txjuju._utils import Executable11from txjuju._utils import Executable
12import txjuju.cli
13import yaml
814
9from fakejuju.failures import Failures15from fakejuju.failures import Failures
10from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju16from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju
@@ -44,9 +50,10 @@
44 def test_all_args(self):50 def test_all_args(self):
45 """set_envvars() works correctly when given all args."""51 """set_envvars() works correctly when given all args."""
46 envvars = {}52 envvars = {}
47 set_envvars(envvars, "/spam/failures", "/eggs/logsdir")53 set_envvars(envvars, "/spam", "/spam/failures", "/eggs/logsdir")
4854
49 self.assertEqual(envvars, {55 self.assertEqual(envvars, {
56 "FAKE_JUJU_DATA_DIR": "/spam",
50 "FAKE_JUJU_FAILURES": "/spam/failures",57 "FAKE_JUJU_FAILURES": "/spam/failures",
51 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",58 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",
52 })59 })
@@ -57,6 +64,7 @@
57 set_envvars(envvars)64 set_envvars(envvars)
5865
59 self.assertEqual(envvars, {66 self.assertEqual(envvars, {
67 "FAKE_JUJU_DATA_DIR": "",
60 "FAKE_JUJU_FAILURES": "",68 "FAKE_JUJU_FAILURES": "",
61 "FAKE_JUJU_LOGS_DIR": "",69 "FAKE_JUJU_LOGS_DIR": "",
62 })70 })
@@ -64,9 +72,10 @@
64 def test_start_empty(self):72 def test_start_empty(self):
65 """set_envvars() sets all values on an empty dict."""73 """set_envvars() sets all values on an empty dict."""
66 envvars = {}74 envvars = {}
67 set_envvars(envvars, "x", "y")75 set_envvars(envvars, "w", "x", "y")
6876
69 self.assertEqual(envvars, {77 self.assertEqual(envvars, {
78 "FAKE_JUJU_DATA_DIR": "w",
70 "FAKE_JUJU_FAILURES": "x",79 "FAKE_JUJU_FAILURES": "x",
71 "FAKE_JUJU_LOGS_DIR": "y",80 "FAKE_JUJU_LOGS_DIR": "y",
72 })81 })
@@ -74,10 +83,11 @@
74 def test_no_collisions(self):83 def test_no_collisions(self):
75 """set_envvars() sets all values when none are set yet."""84 """set_envvars() sets all values when none are set yet."""
76 envvars = {"SPAM": "eggs"}85 envvars = {"SPAM": "eggs"}
77 set_envvars(envvars, "x", "y")86 set_envvars(envvars, "w", "x", "y")
7887
79 self.assertEqual(envvars, {88 self.assertEqual(envvars, {
80 "SPAM": "eggs",89 "SPAM": "eggs",
90 "FAKE_JUJU_DATA_DIR": "w",
81 "FAKE_JUJU_FAILURES": "x",91 "FAKE_JUJU_FAILURES": "x",
82 "FAKE_JUJU_LOGS_DIR": "y",92 "FAKE_JUJU_LOGS_DIR": "y",
83 })93 })
@@ -85,12 +95,14 @@
85 def test_empty_to_nonempty(self):95 def test_empty_to_nonempty(self):
86 """set_envvars() updates empty values."""96 """set_envvars() updates empty values."""
87 envvars = {97 envvars = {
98 "FAKE_JUJU_DATA_DIR": "",
88 "FAKE_JUJU_FAILURES": "",99 "FAKE_JUJU_FAILURES": "",
89 "FAKE_JUJU_LOGS_DIR": "",100 "FAKE_JUJU_LOGS_DIR": "",
90 }101 }
91 set_envvars(envvars, "x", "y")102 set_envvars(envvars, "w", "x", "y")
92103
93 self.assertEqual(envvars, {104 self.assertEqual(envvars, {
105 "FAKE_JUJU_DATA_DIR": "w",
94 "FAKE_JUJU_FAILURES": "x",106 "FAKE_JUJU_FAILURES": "x",
95 "FAKE_JUJU_LOGS_DIR": "y",107 "FAKE_JUJU_LOGS_DIR": "y",
96 })108 })
@@ -98,12 +110,14 @@
98 def test_nonempty_to_nonempty(self):110 def test_nonempty_to_nonempty(self):
99 """set_envvars() overwrites existing values."""111 """set_envvars() overwrites existing values."""
100 envvars = {112 envvars = {
113 "FAKE_JUJU_DATA_DIR": "spam",
101 "FAKE_JUJU_FAILURES": "spam",114 "FAKE_JUJU_FAILURES": "spam",
102 "FAKE_JUJU_LOGS_DIR": "ham",115 "FAKE_JUJU_LOGS_DIR": "ham",
103 }116 }
104 set_envvars(envvars, "x", "y")117 set_envvars(envvars, "w", "x", "y")
105118
106 self.assertEqual(envvars, {119 self.assertEqual(envvars, {
120 "FAKE_JUJU_DATA_DIR": "w",
107 "FAKE_JUJU_FAILURES": "x",121 "FAKE_JUJU_FAILURES": "x",
108 "FAKE_JUJU_LOGS_DIR": "y",122 "FAKE_JUJU_LOGS_DIR": "y",
109 })123 })
@@ -111,12 +125,14 @@
111 def test_nonempty_to_empty(self):125 def test_nonempty_to_empty(self):
112 """set_envvars() with no args "unsets" existing values."""126 """set_envvars() with no args "unsets" existing values."""
113 envvars = {127 envvars = {
128 "FAKE_JUJU_DATA_DIR": "w",
114 "FAKE_JUJU_FAILURES": "x",129 "FAKE_JUJU_FAILURES": "x",
115 "FAKE_JUJU_LOGS_DIR": "y",130 "FAKE_JUJU_LOGS_DIR": "y",
116 }131 }
117 set_envvars(envvars)132 set_envvars(envvars)
118133
119 self.assertEqual(envvars, {134 self.assertEqual(envvars, {
135 "FAKE_JUJU_DATA_DIR": "",
120 "FAKE_JUJU_FAILURES": "",136 "FAKE_JUJU_FAILURES": "",
121 "FAKE_JUJU_LOGS_DIR": "",137 "FAKE_JUJU_LOGS_DIR": "",
122 })138 })
@@ -131,7 +147,7 @@
131147
132 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")148 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")
133 self.assertEqual(juju.version, "1.25.6")149 self.assertEqual(juju.version, "1.25.6")
134 self.assertEqual(juju.cfgdir, "/a/juju/home")150 self.assertEqual(juju.datadir, "/a/juju/home")
135 self.assertEqual(juju.logsdir, "/logs/dir")151 self.assertEqual(juju.logsdir, "/logs/dir")
136 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")152 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")
137153
@@ -141,19 +157,20 @@
141157
142 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")158 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")
143 self.assertEqual(juju.version, "1.25.6")159 self.assertEqual(juju.version, "1.25.6")
144 self.assertEqual(juju.cfgdir, "/my/juju/home")160 self.assertEqual(juju.datadir, "/my/juju/home")
145 self.assertEqual(juju.logsdir, "/my/juju/home")161 self.assertEqual(juju.logsdir, "/my/juju/home")
146 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")162 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
147163
148 def test_full(self):164 def test_full(self):
149 """FakeJuju() works correctly when given all args."""165 """FakeJuju() works correctly when given all args."""
150 cfgdir = "/my/juju/home"166 datadir = "/my/juju/home"
151 failures = Failures(cfgdir)167 failures = Failures(datadir)
152 juju = FakeJuju("/fake-juju", "1.25.6", cfgdir, "/some/logs", failures)168 juju = FakeJuju(
169 "/fake-juju", "1.25.6", datadir, "/some/logs", failures)
153170
154 self.assertEqual(juju.filename, "/fake-juju")171 self.assertEqual(juju.filename, "/fake-juju")
155 self.assertEqual(juju.version, "1.25.6")172 self.assertEqual(juju.version, "1.25.6")
156 self.assertEqual(juju.cfgdir, cfgdir)173 self.assertEqual(juju.datadir, datadir)
157 self.assertEqual(juju.logsdir, "/some/logs")174 self.assertEqual(juju.logsdir, "/some/logs")
158 self.assertIs(juju.failures, failures)175 self.assertIs(juju.failures, failures)
159176
@@ -163,7 +180,7 @@
163180
164 self.assertEqual(juju.filename, "/fake-juju")181 self.assertEqual(juju.filename, "/fake-juju")
165 self.assertEqual(juju.version, "1.25.6")182 self.assertEqual(juju.version, "1.25.6")
166 self.assertEqual(juju.cfgdir, "/my/juju/home")183 self.assertEqual(juju.datadir, "/my/juju/home")
167 self.assertEqual(juju.logsdir, "/my/juju/home")184 self.assertEqual(juju.logsdir, "/my/juju/home")
168 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")185 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
169186
@@ -174,7 +191,7 @@
174 juju_unicode = FakeJuju(191 juju_unicode = FakeJuju(
175 u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/..."))192 u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/..."))
176193
177 for name in ('filename version cfgdir logsdir'.split()):194 for name in ('filename version datadir logsdir'.split()):
178 self.assertIsInstance(getattr(juju_str, name), str)195 self.assertIsInstance(getattr(juju_str, name), str)
179 self.assertIsInstance(getattr(juju_unicode, name), unicode)196 self.assertIsInstance(getattr(juju_unicode, name), unicode)
180197
@@ -192,8 +209,8 @@
192 with self.assertRaises(ValueError):209 with self.assertRaises(ValueError):
193 FakeJuju("/fake-juju", "", "/my/juju/home")210 FakeJuju("/fake-juju", "", "/my/juju/home")
194211
195 def test_missing_cfgdir(self):212 def test_missing_datadir(self):
196 """FakeJuju() fails if cfgdir is None or empty."""213 """FakeJuju() fails if datadir is None or empty."""
197 with self.assertRaises(ValueError):214 with self.assertRaises(ValueError):
198 FakeJuju("/fake-juju", "1.25.6", None)215 FakeJuju("/fake-juju", "1.25.6", None)
199 with self.assertRaises(ValueError):216 with self.assertRaises(ValueError):
@@ -226,44 +243,208 @@
226 def test_cli_full(self):243 def test_cli_full(self):
227 """FakeJuju.cli() works correctly when given all args."""244 """FakeJuju.cli() works correctly when given all args."""
228 juju = FakeJuju("/fake-juju", "1.25.6", "/x")245 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
229 cli = juju.cli({"SPAM": "eggs"})246 cli = juju.cli("/y", {"SPAM": "eggs"})
230247
231 self.assertEqual(248 self.assertEqual(
232 cli._exe,249 cli._exe,
233 Executable("/fake-juju", {250 Executable("/fake-juju", {
234 "SPAM": "eggs",251 "SPAM": "eggs",
252 "FAKE_JUJU_DATA_DIR": "/x",
235 "FAKE_JUJU_FAILURES": "/x/juju-failures",253 "FAKE_JUJU_FAILURES": "/x/juju-failures",
236 "FAKE_JUJU_LOGS_DIR": "/x",254 "FAKE_JUJU_LOGS_DIR": "/x",
237 "JUJU_HOME": "/x",255 "JUJU_HOME": "/y",
238 }),256 }),
239 )257 )
240258
241 def test_cli_minimal(self):259 def test_cli_minimal(self):
242 """FakeJuju.cli() works correctly when given minimal args."""260 """FakeJuju.cli() works correctly when given minimal args."""
243 juju = FakeJuju("/fake-juju", "1.25.6", "/x")261 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
244 cli = juju.cli()262 cli = juju.cli("/y")
245263
246 self.assertEqual(264 self.assertEqual(
247 cli._exe,265 cli._exe,
248 Executable("/fake-juju", dict(os.environ, **{266 Executable("/fake-juju", dict(os.environ, **{
267 "FAKE_JUJU_DATA_DIR": "/x",
249 "FAKE_JUJU_FAILURES": "/x/juju-failures",268 "FAKE_JUJU_FAILURES": "/x/juju-failures",
250 "FAKE_JUJU_LOGS_DIR": "/x",269 "FAKE_JUJU_LOGS_DIR": "/x",
251 "JUJU_HOME": "/x",270 "JUJU_HOME": "/y",
252 })),271 })),
253 )272 )
254273
255 def test_cli_juju1(self):274 def test_cli_juju1(self):
256 """FakeJuju.cli() works correctly for Juju 1.x."""275 """FakeJuju.cli() works correctly for Juju 1.x."""
257 juju = FakeJuju.from_version("1.25.6", "/x")276 juju = FakeJuju.from_version("1.25.6", "/x")
258 cli = juju.cli()277 cli = juju.cli("/y")
259278
260 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/x")279 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/y")
261 self.assertIsInstance(cli._juju, _juju1.CLIHooks)280 self.assertIsInstance(cli._juju, _juju1.CLIHooks)
262281
263 def test_cli_juju2(self):282 def test_cli_juju2(self):
264 """FakeJuju.cli() works correctly for Juju 2.x."""283 """FakeJuju.cli() works correctly for Juju 2.x."""
265 juju = FakeJuju.from_version("2.0.0", "/x")284 juju = FakeJuju.from_version("2.0.0", "/x")
266 cli = juju.cli()285 cli = juju.cli("/y")
267286
268 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/x")287 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/y")
269 self.assertIsInstance(cli._juju, _juju2.CLIHooks)288 self.assertIsInstance(cli._juju, _juju2.CLIHooks)
289
290 def test_bootstrap(self):
291 """FakeJuju.bootstrap() bootstraps from scratch using fake-juju."""
292 expected = txjuju.cli.APIInfo(
293 endpoints=['localhost:12727'],
294 user='admin',
295 password='dummy-secret',
296 model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d',
297 )
298 version = "1.25.6"
299 with tempdir() as testdir:
300 bindir = os.path.join(testdir, "bin")
301 datadir = os.path.join(testdir, "fakejuju")
302 cfgdir = os.path.join(testdir, ".juju")
303
304 logfilename = write_fakejuju_script(
305 version, bindir, datadir, cfgdir, expected)
306 fakejuju = FakeJuju.from_version(version, cfgdir, bindir=bindir)
307
308 cli, api_info = fakejuju.bootstrap("spam", cfgdir, "secret")
309
310 files = []
311 files.extend(os.path.join(os.path.basename(datadir), name)
312 for name in os.listdir(datadir))
313 files.extend(os.path.join(os.path.basename(cfgdir), name)
314 for name in os.listdir(cfgdir))
315 with open(os.path.join(cfgdir, "environments.yaml")) as envfile:
316 data = envfile.read()
317
318 cli.destroy_controller()
319 with open(logfilename) as logfile:
320 calls = [line.strip() for line in logfile]
321
322 self.maxDiff = None
323 self.assertEqual(api_info, {
324 'controller': expected,
325 None: expected._replace(model_uuid=None),
326 })
327 subcommands = []
328 for call in calls:
329 args = call.split()
330 self.assertEqual(os.path.basename(args[0]), "fake-juju-" + version)
331 subcommands.append(args[1])
332 self.assertEqual(subcommands, [
333 "bootstrap",
334 "api-info",
335 "destroy-environment",
336 ])
337 self.assertItemsEqual(files, [
338 '.juju/environments',
339 '.juju/environments.yaml',
340 'fakejuju/cert.ca',
341 'fakejuju/fake-juju.log',
342 'fakejuju/fakejuju',
343 'fakejuju/fifo',
344 ])
345 self.assertEqual(yaml.load(data), {
346 "environments": {
347 "spam": {
348 "admin-secret": "secret",
349 "default-series": "trusty",
350 "type": "dummy",
351 },
352 },
353 })
354
355 def test_is_bootstrapped_true(self):
356 """FakeJuju.is_bootstrapped() returns True if the fifo file exists."""
357 with tempdir() as datadir:
358 fakejuju = FakeJuju.from_version("1.25.6", datadir)
359 with open(fakejuju.fifo, "w"):
360 pass
361 result = fakejuju.is_bootstrapped()
362
363 self.assertTrue(result)
364
365 def test_is_bootstrapped_false(self):
366 """FakeJuju.is_bootstrapped() returns False if the fifo is gone."""
367 with tempdir() as datadir:
368 fakejuju = FakeJuju.from_version("1.25.6", datadir)
369 result = fakejuju.is_bootstrapped()
370
371 self.assertFalse(result)
372
373 def test_is_bootstrapped_datadir_missing(self):
374 """FakeJuju.is_bootstrapped() returns False if the data dir is gone."""
375 fakejuju = FakeJuju.from_version("1.25.6", "/tmp/fakejuju-no-exist")
376 result = fakejuju.is_bootstrapped()
377
378 self.assertFalse(result)
379
380
381FAKE_JUJU_SCRIPT = """\
382#!/usr/bin/env python
383
384import os.path
385import sys
386
387with open("{logfile}", "a") as logfile:
388 logfile.write(" ".join(sys.argv) + "\\n")
389
390if sys.argv[1] == "bootstrap":
391 for filename in ("cert.ca", "fake-juju.log", "fakejuju", "fifo"):
392 with open(os.path.join("{datadir}", filename), "w"):
393 pass # Touch the file.
394 for filename in ("environments",):
395 with open(os.path.join("{cfgdir}", filename), "w"):
396 pass # Touch the file.
397elif sys.argv[1] in ("api-info", "show-controller"):
398 print('''{output}''')
399
400"""
401
402
403def write_fakejuju_script(version, bindir, datadir, cfgdir, api_info):
404 if version.startswith("1."):
405 raw_api_info = {
406 "state-servers": api_info.endpoints,
407 "user": api_info.user,
408 "password": api_info.password,
409 "environ-uuid": api_info.model_uuid,
410 }
411 else:
412 raw_api_info = {
413 "details": {
414 "api-endpoints": api_info.endpoints,
415 },
416 "account": {
417 "user": api_info.user + "@local",
418 "password": api_info.password,
419 },
420 "models": {
421 "controller": {
422 "uuid": api_info.model_uuid,
423 },
424 "default": {
425 "uuid": api_info.model_uuid,
426 },
427 },
428 }
429 output = json.dumps(raw_api_info)
430
431 logfile = os.path.join(bindir, "calls.log")
432 script = FAKE_JUJU_SCRIPT.format(
433 datadir=datadir, cfgdir=cfgdir, logfile=logfile, output=output)
434 filename = get_filename(version, bindir)
435 os.makedirs(os.path.dirname(filename))
436 with open(filename, "w") as scriptfile:
437 scriptfile.write(script)
438 os.chmod(filename, 0o755)
439 os.makedirs(datadir)
440
441 return logfile
442
443
444@contextmanager
445def tempdir():
446 cfgdir = tempfile.mkdtemp("fakejuju-test-")
447 try:
448 yield cfgdir
449 finally:
450 shutil.rmtree(cfgdir)
270451
=== modified file 'tests/test_fake.py'
--- tests/test_fake.py 2016-09-15 20:38:52 +0000
+++ tests/test_fake.py 2016-10-25 17:25:06 +0000
@@ -108,7 +108,7 @@
108 output = subprocess.check_output(args, env=env)108 output = subprocess.check_output(args, env=env)
109 api_info = json.loads(output.decode())109 api_info = json.loads(output.decode())
110 endpoint = str(api_info[name]["details"]["api-endpoints"][0])110 endpoint = str(api_info[name]["details"]["api-endpoints"][0])
111 model = api_info[name]["current-model"]111 model = api_info[name]["current-model"].split("/", 1)[-1]
112 uuid = api_info[name]["models"][model]["uuid"]112 uuid = api_info[name]["models"][model]["uuid"]
113 password = api_info[name]["account"]["password"]113 password = api_info[name]["account"]["password"]
114 return endpoint, uuid, password114 return endpoint, uuid, password

Subscribers

People subscribed via source and target branches

to all changes: