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

Proposed by Eric Snow
Status: Merged
Approved by: Eric Snow
Approved revision: 59
Merged at revision: 43
Proposed branch: lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Merge into: lp:~landscape/fake-juju/trunk-old
Prerequisite: lp:~ericsnowcurrently/fake-juju/testing-cleanup
Diff against target: 1474 lines (+604/-311)
5 files modified
1.25.6/fake-juju.go (+251/-106)
2.0-beta17/fake-juju.go (+257/-140)
Makefile (+1/-1)
python/fakejuju/fakejuju.py (+32/-26)
python/fakejuju/tests/test_fakejuju.py (+63/-38)
To merge this branch: bzr merge lp:~ericsnowcurrently/fake-juju/fake-juju-data-dir
Reviewer Review Type Date Requested Status
🤖 Landscape Builder test results Approve
Данило Шеган (community) Approve
Chad Smith Approve
Review via email: mp+308870@code.launchpad.net

This proposal supersedes a proposal from 2016-10-19.

Commit message

Add a FAKE_JUJU_DATA_DIR environment variable.

This change also does some clean-up. Note that the changes are almost
completely the same for the two Juju versions, so reviewing shouldn't
be as bad as the line-count might imply. If fake-juju had better code
re-use then this patch would be much smaller.

Description of the change

Add a FAKE_JUJU_DATA_DIR environment variable.

This change also does some clean-up. Note that the changes are almost
completely the same for the two Juju versions, so reviewing shouldn't
be as bad as the line-count might imply. If fake-juju had better code
re-use then this patch would be much smaller.

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

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

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

Merge from parent.

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

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

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

Looks good, tests pass. +1

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

Clarify some names.

56. By Eric Snow

Add missing doc comments.

57. By Eric Snow

Clarify some names.

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

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

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

Merge from parent.

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

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

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

Merge from trunk.

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

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

review: Approve (test results)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '1.25.6/fake-juju.go'
--- 1.25.6/fake-juju.go 2016-10-18 16:35:29 +0000
+++ 1.25.6/fake-juju.go 2016-10-26 18:45:46 +0000
@@ -37,45 +37,42 @@
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 Password string
56 WorkDir string
57 EndpointAddr string
58 Uuid string
59 CACert string
60}53}
6154
62func handleCommand(command string) error {55func handleCommand(command string) error {
56 filenames := newFakeJujuFilenames("", "", "")
63 if command == "bootstrap" {57 if command == "bootstrap" {
64 return bootstrap()58 return bootstrap(filenames)
65 }59 }
66 if command == "api-endpoints" {60 if command == "api-endpoints" {
67 return apiEndpoints()61 return apiEndpoints(filenames)
68 }62 }
69 if command == "api-info" {63 if command == "api-info" {
70 return apiInfo()64 return apiInfo(filenames)
71 }65 }
72 if command == "destroy-environment" {66 if command == "destroy-environment" {
73 return destroyEnvironment()67 return destroyEnvironment(filenames)
74 }68 }
75 return errors.New("command not found")69 return errors.New("command not found")
76}70}
7771
78func bootstrap() error {72func bootstrap(filenames fakejujuFilenames) error {
73 if err := filenames.ensureDirsExist(); err != nil {
74 return err
75 }
79 envName, config, err := environmentNameAndConfig()76 envName, config, err := environmentNameAndConfig()
80 if err != nil {77 if err != nil {
81 return err78 return err
@@ -91,10 +88,16 @@
91 return err88 return err
92 }89 }
93 command.Start()90 command.Start()
94 apiInfo, err := parseApiInfo(envName, stdout)91
92 result, err := parseApiInfo(stdout)
95 if err != nil {93 if err != nil {
96 return err94 return err
97 }95 }
96 if err := result.apply(filenames, envName); err != nil {
97 return err
98 }
99 apiInfo := result.apiInfo()
100
98 dialOpts := api.DialOpts{101 dialOpts := api.DialOpts{
99 DialAddressInterval: 50 * time.Millisecond,102 DialAddressInterval: 50 * time.Millisecond,
100 Timeout: 5 * time.Second,103 Timeout: 5 * time.Second,
@@ -124,8 +127,8 @@
124 return errors.New("invalid delta")127 return errors.New("invalid delta")
125}128}
126129
127func apiEndpoints() error {130func apiEndpoints(filenames fakejujuFilenames) error {
128 info, err := readProcessInfo()131 info, err := readProcessInfo(filenames)
129 if err != nil {132 if err != nil {
130 return err133 return err
131 }134 }
@@ -133,8 +136,8 @@
133 return nil136 return nil
134}137}
135138
136func apiInfo() error {139func apiInfo(filenames fakejujuFilenames) error {
137 info, err := readProcessInfo()140 info, err := readProcessInfo(filenames)
138 if err != nil {141 if err != nil {
139 return err142 return err
140 }143 }
@@ -143,13 +146,13 @@
143 return nil146 return nil
144}147}
145148
146func destroyEnvironment() error {149func destroyEnvironment(filenames fakejujuFilenames) error {
147 info, err := readProcessInfo()150 info, err := readProcessInfo(filenames)
148 if err != nil {151 if err != nil {
149 return err152 return err
150 }153 }
151 fifoPath := filepath.Join(info.WorkDir, "fifo")154 filenames = newFakeJujuFilenames("", "", info.WorkDir)
152 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)155 fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600)
153 if err != nil {156 if err != nil {
154 return err157 return err
155 }158 }
@@ -177,94 +180,235 @@
177 return envName, config, nil180 return envName, config, nil
178}181}
179182
180func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {183// processInfo holds all the information that fake-juju uses internally.
184type processInfo struct {
185 Username string
186 Password string
187 WorkDir string
188 EndpointAddr string
189 Uuid string
190 CACert []byte
191}
192
193func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
194 infoPath := filenames.infoFile()
195 data, err := ioutil.ReadFile(infoPath)
196 if err != nil {
197 return nil, err
198 }
199 info := &processInfo{}
200 err = goyaml.Unmarshal(data, info)
201 if err != nil {
202 return nil, err
203 }
204 return info, nil
205}
206
207func (info processInfo) write(infoPath string) error {
208 data, _ := goyaml.Marshal(&info)
209 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
210 return err
211 }
212 return nil
213}
214
215// fakejujuFilenames encapsulates the paths to all the directories and
216// files that are relevant to fake-juju.
217type fakejujuFilenames struct {
218 datadir string
219 logsdir string
220}
221
222func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
223 if datadir == "" {
224 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
225 if datadir == "" {
226 if jujucfgdir == "" {
227 jujucfgdir = os.Getenv("JUJU_HOME")
228 }
229 datadir = jujucfgdir
230 }
231 }
232 if logsdir == "" {
233 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
234 if logsdir == "" {
235 logsdir = datadir
236 }
237 }
238 return fakejujuFilenames{datadir, logsdir}
239}
240
241func (fj fakejujuFilenames) ensureDirsExist() error {
242 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
243 return err
244 }
245 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
246 return err
247 }
248 return nil
249}
250
251// infoFile() returns the path to the file that fake-juju uses as
252// its persistent storage for internal data.
253func (fj fakejujuFilenames) infoFile() string {
254 return filepath.Join(fj.datadir, "fakejuju")
255}
256
257// logsFile() returns the path to the file where fake-juju writes
258// its logs. Note that the normal Juju logs are not written here.
259func (fj fakejujuFilenames) logsFile() string {
260 return filepath.Join(fj.logsdir, "fake-juju.log")
261}
262
263// fifoFile() returns the path to the FIFO file used by fake-juju.
264// The FIFO is used by the fake-juju subcommands to interact with
265// the daemon.
266func (fj fakejujuFilenames) fifoFile() string {
267 return filepath.Join(fj.datadir, "fifo")
268}
269
270// caCertFile() returns the path to the file holding the CA certificate
271// used by the Juju API server. fake-juju writes the cert there as a
272// convenience for users. It is not actually used for anything.
273func (fj fakejujuFilenames) caCertFile() string {
274 return filepath.Join(fj.datadir, "cert.ca")
275}
276
277// bootstrapResult encapsulates all significant information that came
278// from bootstrapping an environment.
279type bootstrapResult struct {
280 dummyEnvName string
281 cfgdir string
282 uuid string
283 username string
284 password string
285 addresses []string
286 caCert []byte
287}
288
289// apiInfo() composes the Juju API info corresponding to the result.
290func (br bootstrapResult) apiInfo() *api.Info {
291 return &api.Info{
292 Addrs: br.addresses,
293 Tag: names.NewLocalUserTag(br.username),
294 Password: br.password,
295 CACert: string(br.caCert),
296 EnvironTag: names.NewEnvironTag(br.uuid),
297 }
298}
299
300// fakeJujuInfo() composes, from the result, the set of information
301// that fake-juju should use internally.
302func (br bootstrapResult) fakeJujuInfo() *processInfo {
303 return &processInfo{
304 Username: br.username,
305 Password: br.password,
306 WorkDir: br.cfgdir,
307 EndpointAddr: br.addresses[0],
308 Uuid: br.uuid,
309 CACert: br.caCert,
310 }
311}
312
313// logsSymlinkFilenames() determines the source and target paths for
314// a symlink to the fake-juju logs file. Such a symlink is relevant
315// because the fake-juju daemon may not know where the log file is
316// meant to go. It defaults to putting the log file in the default Juju
317// config dir. In that case, a symlink should be created from there to
318// the user-defined Juju config dir ($JUJU_HOME).
319func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) {
320 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" || os.Getenv("FAKE_JUJU_DATA_DIR") != "" {
321 return "", ""
322 }
323
324 filenames := newFakeJujuFilenames("", "", br.cfgdir)
325 source = filenames.logsFile()
326 target = targetLogsFile
327 return source, target
328}
329
330// jenvSymlinkFilenames() determines the source and target paths for
331// a symlink to the .jenv file for the identified environment.
332func (br bootstrapResult) jenvSymlinkFilenames(jujuHome, envName string) (source, target string) {
333 if jujuHome == "" || envName == "" {
334 return "", ""
335 }
336
337 source = filepath.Join(br.cfgdir, "environments", br.dummyEnvName+".jenv")
338 target = filepath.Join(jujuHome, "environments", envName+".jenv")
339 return source, target
340}
341
342// apply() writes out the information from the bootstrap result to the
343// various files identified by the provided filenames.
344func (br bootstrapResult) apply(filenames fakejujuFilenames, envName string) error {
345 if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil {
346 return err
347 }
348
349 logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile())
350 if logsSource != "" && logsTarget != "" {
351 if err := os.Symlink(logsSource, logsTarget); err != nil {
352 return err
353 }
354 }
355
356 jenvSource, jenvTarget := br.jenvSymlinkFilenames(os.Getenv("JUJU_HOME"), envName)
357 if jenvSource != "" && jenvTarget != "" {
358 if err := os.MkdirAll(filepath.Dir(jenvTarget), 0755); err != nil {
359 return err
360 }
361 if err := os.Symlink(jenvSource, jenvTarget); err != nil {
362 return err
363 }
364 }
365
366 if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil {
367 return err
368 }
369
370 return nil
371}
372
373// See github.com/juju/juju/blob/juju/testing/conn.go.
374const dummyEnvName = "dummyenv"
375
376func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
181 buffer := bufio.NewReader(stdout)377 buffer := bufio.NewReader(stdout)
378
182 line, _, err := buffer.ReadLine()379 line, _, err := buffer.ReadLine()
183 if err != nil {380 if err != nil {
184 return nil, err381 return nil, err
185 }382 }
186 uuid := string(line)383 uuid := string(line)
187 environTag := names.NewEnvironTag(uuid)384
188 line, _, err = buffer.ReadLine()385 line, _, err = buffer.ReadLine()
189 if err != nil {386 if err != nil {
190 return nil, err387 return nil, err
191 }388 }
192 workDir := string(line)389 workDir := string(line)
390
193 store, err := configstore.NewDisk(workDir)391 store, err := configstore.NewDisk(workDir)
194 if err != nil {392 if err != nil {
195 return nil, err393 return nil, err
196 }394 }
197 info, err := store.ReadInfo("dummyenv")395 info, err := store.ReadInfo(dummyEnvName)
198 if err != nil {396 if err != nil {
199 return nil, err397 return nil, err
200 }398 }
399
201 credentials := info.APICredentials()400 credentials := info.APICredentials()
202 endpoint := info.APIEndpoint()401 endpoint := info.APIEndpoint()
203 addresses := endpoint.Addresses402 result := &bootstrapResult{
204 apiInfo := &api.Info{403 dummyEnvName: dummyEnvName,
205 Addrs: addresses,404 cfgdir: workDir,
206 Tag: names.NewLocalUserTag(credentials.User),405 uuid: uuid,
207 Password: credentials.Password,406 username: credentials.User,
208 CACert: endpoint.CACert,407 password: credentials.Password,
209 EnvironTag: environTag,408 addresses: endpoint.Addresses,
210 }409 caCert: []byte(endpoint.CACert),
211 err = writeProcessInfo(envName, &processInfo{410 }
212 Username: credentials.User,411 return result, nil
213 Password: credentials.Password,
214 WorkDir: workDir,
215 EndpointAddr: addresses[0],
216 Uuid: uuid,
217 CACert: endpoint.CACert,
218 })
219 if err != nil {
220 return nil, err
221 }
222 return apiInfo, nil
223}
224
225func readProcessInfo() (*processInfo, error) {
226 infoPath := filepath.Join(os.Getenv("JUJU_HOME"), "fakejuju")
227 data, err := ioutil.ReadFile(infoPath)
228 if err != nil {
229 return nil, err
230 }
231 info := &processInfo{}
232 err = goyaml.Unmarshal(data, info)
233 if err != nil {
234 return nil, err
235 }
236 return info, nil
237}
238
239func writeProcessInfo(envName string, info *processInfo) error {
240 var err error
241 jujuHome := os.Getenv("JUJU_HOME")
242 infoPath := filepath.Join(jujuHome, "fakejuju")
243 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")
244 if logsDir == "" {
245 logsDir = jujuHome
246 }
247 logPath := filepath.Join(logsDir, "fake-juju.log")
248 caCertPath := filepath.Join(jujuHome, "cert.ca")
249 envPath := filepath.Join(jujuHome, "environments")
250 os.Mkdir(envPath, 0755)
251 jEnvPath := filepath.Join(envPath, envName+".jenv")
252 data, _ := goyaml.Marshal(info)
253 if os.Getenv("FAKE_JUJU_LOGS_DIR") == "" {
254 err = os.Symlink(filepath.Join(info.WorkDir, "fake-juju.log"), logPath)
255 if err != nil {
256 return err
257 }
258 }
259 err = os.Symlink(filepath.Join(info.WorkDir, "environments/dummyenv.jenv"), jEnvPath)
260 if err != nil {
261 return err
262 }
263 err = ioutil.WriteFile(infoPath, data, 0644)
264 if err != nil {
265 return err
266 }
267 return ioutil.WriteFile(caCertPath, []byte(info.CACert), 0644)
268}412}
269413
270// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment414// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -304,12 +448,16 @@
304 return failuresInfo, nil448 return failuresInfo, nil
305}449}
306450
451//===================================================================
452// The fake-juju daemon (started by bootstrap) is found here. It is
453// implemented as a test suite.
454
307type FakeJujuSuite struct {455type FakeJujuSuite struct {
308 jujutesting.JujuConnSuite456 jujutesting.JujuConnSuite
309457
310 instanceCount int458 instanceCount int
311 machineStarted map[string]bool459 machineStarted map[string]bool
312 fifoPath string460 filenames fakejujuFilenames
313 logFile *os.File461 logFile *os.File
314}462}
315463
@@ -376,15 +524,11 @@
376 c.Assert(err, gc.IsNil)524 c.Assert(err, gc.IsNil)
377 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))525 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
378526
379 s.fifoPath = filepath.Join(jujuHome, "fifo")527 s.filenames = newFakeJujuFilenames("", "", jujuHome)
380 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)528 syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0)
381529
382 // Logging530 // Logging
383 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")531 logPath := s.filenames.logsFile()
384 if logsDir == "" {
385 logsDir = jujuHome
386 }
387 logPath := filepath.Join(logsDir, "fake-juju.log")
388 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)532 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
389 c.Assert(err, gc.IsNil)533 c.Assert(err, gc.IsNil)
390534
@@ -404,16 +548,17 @@
404}548}
405549
406func (s *FakeJujuSuite) TestStart(c *gc.C) {550func (s *FakeJujuSuite) TestStart(c *gc.C) {
551 fifoPath := s.filenames.fifoFile()
407 watcher := s.State.Watch()552 watcher := s.State.Watch()
408 go func() {553 go func() {
409 log.Println("Open commands FIFO", s.fifoPath)554 log.Println("Open commands FIFO", fifoPath)
410 fd, err := os.Open(s.fifoPath)555 fd, err := os.Open(fifoPath)
411 if err != nil {556 if err != nil {
412 log.Println("Failed to open commands FIFO")557 log.Println("Failed to open commands FIFO")
413 }558 }
414 c.Assert(err, gc.IsNil)559 c.Assert(err, gc.IsNil)
415 scanner := bufio.NewScanner(fd)560 scanner := bufio.NewScanner(fd)
416 log.Println("Listen for commands on FIFO", s.fifoPath)561 log.Println("Listen for commands on FIFO", fifoPath)
417 scanner.Scan()562 scanner.Scan()
418 log.Println("Stopping fake-juju")563 log.Println("Stopping fake-juju")
419 watcher.Stop()564 watcher.Stop()
420565
=== modified file '2.0-beta17/fake-juju.go'
--- 2.0-beta17/fake-juju.go 2016-09-15 19:05:50 +0000
+++ 2.0-beta17/fake-juju.go 2016-10-26 18:45:46 +0000
@@ -38,46 +38,46 @@
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 // XXX Swap the 2 args for juju-2.0-final.
80 controllerName := os.Args[argc-2]
81 command := exec.Command(os.Args[0])81 command := exec.Command(os.Args[0])
82 command.Env = os.Environ()82 command.Env = os.Environ()
83 command.Env = append(83 command.Env = append(
@@ -89,10 +89,16 @@
89 return err89 return err
90 }90 }
91 command.Start()91 command.Start()
92 apiInfo, err := parseApiInfo(envName, stdout)92
93 result, err := parseApiInfo(stdout)
93 if err != nil {94 if err != nil {
94 return err95 return err
95 }96 }
97 if err := result.apply(filenames, controllerName); err != nil {
98 return err
99 }
100 apiInfo := result.apiInfo()
101
96 dialOpts := api.DialOpts{102 dialOpts := api.DialOpts{
97 DialAddressInterval: 50 * time.Millisecond,103 DialAddressInterval: 50 * time.Millisecond,
98 Timeout: 5 * time.Second,104 Timeout: 5 * time.Second,
@@ -122,8 +128,8 @@
122 return errors.New("invalid delta")128 return errors.New("invalid delta")
123}129}
124130
125func apiInfo() error {131func apiInfo(filenames fakejujuFilenames) error {
126 info, err := readProcessInfo()132 info, err := readProcessInfo(filenames)
127 if err != nil {133 if err != nil {
128 return err134 return err
129 }135 }
@@ -140,13 +146,13 @@
140 return nil146 return nil
141}147}
142148
143func destroyEnvironment() error {149func destroyController(filenames fakejujuFilenames) error {
144 info, err := readProcessInfo()150 info, err := readProcessInfo(filenames)
145 if err != nil {151 if err != nil {
146 return err152 return err
147 }153 }
148 fifoPath := filepath.Join(info.WorkDir, "fifo")154 filenames = newFakeJujuFilenames("", "", info.WorkDir)
149 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)155 fd, err := os.OpenFile(filenames.fifoFile(), os.O_APPEND|os.O_WRONLY, 0600)
150 if err != nil {156 if err != nil {
151 return err157 return err
152 }158 }
@@ -158,13 +164,214 @@
158 return nil164 return nil
159}165}
160166
161func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {167// processInfo holds all the information that fake-juju uses internally.
168type processInfo struct {
169 WorkDir string
170 EndpointAddr string
171 Uuid string
172 CACert []byte
173}
174
175func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
176 infoPath := filenames.infoFile()
177 data, err := ioutil.ReadFile(infoPath)
178 if err != nil {
179 return nil, err
180 }
181 info := &processInfo{}
182 err = goyaml.Unmarshal(data, info)
183 if err != nil {
184 return nil, err
185 }
186 return info, nil
187}
188
189func (info processInfo) write(infoPath string) error {
190 data, _ := goyaml.Marshal(&info)
191 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
192 return err
193 }
194 return nil
195}
196
197// fakejujuFilenames encapsulates the paths to all the directories and
198// files that are relevant to fake-juju.
199type fakejujuFilenames struct {
200 datadir string
201 logsdir string
202}
203
204func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
205 if datadir == "" {
206 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
207 if datadir == "" {
208 if jujucfgdir == "" {
209 jujucfgdir = os.Getenv("JUJU_DATA")
210 }
211 datadir = jujucfgdir
212 }
213 }
214 if logsdir == "" {
215 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
216 if logsdir == "" {
217 logsdir = datadir
218 }
219 }
220 return fakejujuFilenames{datadir, logsdir}
221}
222
223func (fj fakejujuFilenames) ensureDirsExist() error {
224 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
225 return err
226 }
227 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
228 return err
229 }
230 return nil
231}
232
233// infoFile() returns the path to the file that fake-juju uses as
234// its persistent storage for internal data.
235func (fj fakejujuFilenames) infoFile() string {
236 return filepath.Join(fj.datadir, "fakejuju")
237}
238
239// logsFile() returns the path to the file where fake-juju writes
240// its logs. Note that the normal Juju logs are not written here.
241func (fj fakejujuFilenames) logsFile() string {
242 return filepath.Join(fj.logsdir, "fake-juju.log")
243}
244
245// fifoFile() returns the path to the FIFO file used by fake-juju.
246// The FIFO is used by the fake-juju subcommands to interact with
247// the daemon.
248func (fj fakejujuFilenames) fifoFile() string {
249 return filepath.Join(fj.datadir, "fifo")
250}
251
252// caCertFile() returns the path to the file holding the CA certificate
253// used by the Juju API server. fake-juju writes the cert there as a
254// convenience for users. It is not actually used for anything.
255func (fj fakejujuFilenames) caCertFile() string {
256 return filepath.Join(fj.datadir, "cert.ca")
257}
258
259// bootstrapResult encapsulates all significant information that came
260// from bootstrapping a controller.
261type bootstrapResult struct {
262 dummyControllerName string
263 cfgdir string
264 uuid string
265 username string
266 password string
267 addresses []string
268 caCert []byte
269}
270
271// apiInfo() composes the Juju API info corresponding to the result.
272func (br bootstrapResult) apiInfo() *api.Info {
273 return &api.Info{
274 Addrs: br.addresses,
275 Tag: names.NewUserTag(br.username),
276 Password: br.password,
277 CACert: string(br.caCert),
278 ModelTag: names.NewModelTag(br.uuid),
279 }
280}
281
282// fakeJujuInfo() composes, from the result, the set of information
283// that fake-juju should use internally.
284func (br bootstrapResult) fakeJujuInfo() *processInfo {
285 return &processInfo{
286 WorkDir: br.cfgdir,
287 EndpointAddr: br.addresses[0],
288 Uuid: br.uuid,
289 CACert: br.caCert,
290 }
291}
292
293// logsSymlinkFilenames() determines the source and target paths for
294// a symlink to the fake-juju logs file. Such a symlink is relevant
295// because the fake-juju daemon may not know where the log file is
296// meant to go. It defaults to putting the log file in the default Juju
297// config dir. In that case, a symlink should be created from there to
298// the user-defined Juju config dir ($JUJU_DATA).
299func (br bootstrapResult) logsSymlinkFilenames(targetLogsFile string) (source, target string) {
300 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" {
301 return "", ""
302 }
303
304 filenames := newFakeJujuFilenames("", "", br.cfgdir)
305 source = filenames.logsFile()
306 target = targetLogsFile
307 return source, target
308}
309
310// apply() writes out the information from the bootstrap result to the
311// various files identified by the provided filenames.
312func (br bootstrapResult) apply(filenames fakejujuFilenames, controllerName string) error {
313 if err := br.fakeJujuInfo().write(filenames.infoFile()); err != nil {
314 return err
315 }
316
317 logsSource, logsTarget := br.logsSymlinkFilenames(filenames.logsFile())
318 if logsSource != "" && logsTarget != "" {
319 if err := os.Symlink(logsSource, logsTarget); err != nil {
320 return err
321 }
322 }
323
324 if err := br.copyConfig(os.Getenv("JUJU_DATA"), controllerName); err != nil {
325 return err
326 }
327
328 if err := ioutil.WriteFile(filenames.caCertFile(), br.caCert, 0644); err != nil {
329 return err
330 }
331
332 return nil
333}
334
335func (br bootstrapResult) copyConfig(targetCfgDir, controllerName string) error {
336 for _, name := range []string{"controllers.yaml", "models.yaml", "accounts.yaml"} {
337 source := filepath.Join(br.cfgdir, name)
338 target := filepath.Join(targetCfgDir, name)
339
340 input, err := ioutil.ReadFile(source)
341 if err != nil {
342 return err
343 }
344 // Generated configuration by test fixtures has the controller name
345 // hard-coded to "kontroll". A simple replace should fix this for
346 // clients using this config and expecting a specific controller
347 // name.
348 output := strings.Replace(string(input), dummyControllerName, controllerName, -1)
349 err = ioutil.WriteFile(target, []byte(output), 0644)
350 if err != nil {
351 return err
352 }
353 }
354
355 current := filepath.Join(targetCfgDir, "current-controller")
356 if err := ioutil.WriteFile(current, []byte(controllerName), 0644); err != nil {
357 return err
358 }
359
360 return nil
361}
362
363// See github.com/juju/juju/blob/juju/testing/conn.go.
364const dummyControllerName = "kontroll"
365
366func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
162 buffer := bufio.NewReader(stdout)367 buffer := bufio.NewReader(stdout)
368
163 line, _, err := buffer.ReadLine()369 line, _, err := buffer.ReadLine()
164 if err != nil {370 if err != nil {
165 return nil, err371 return nil, err
166 }372 }
167 uuid := string(line)373 uuid := string(line)
374
168 line, _, err = buffer.ReadLine()375 line, _, err = buffer.ReadLine()
169 if err != nil {376 if err != nil {
170 return nil, err377 return nil, err
@@ -175,8 +382,8 @@
175 store := jujuclient.NewFileClientStore()382 store := jujuclient.NewFileClientStore()
176 // hard-coded value in juju testing383 // hard-coded value in juju testing
177 // This will be replaced in JUJU_DATA copy of the juju client config.384 // This will be replaced in JUJU_DATA copy of the juju client config.
178 currentController := "kontroll"385 currentController := dummyControllerName
179 one, err := store.ControllerByName("kontroll")386 one, err := store.ControllerByName(currentController)
180 if err != nil {387 if err != nil {
181 return nil, err388 return nil, err
182 }389 }
@@ -185,108 +392,17 @@
185 if err != nil {392 if err != nil {
186 return nil, err393 return nil, err
187 }394 }
188 apiInfo := &api.Info{395
189 Addrs: one.APIEndpoints,396 result := &bootstrapResult{
190 Tag: names.NewUserTag(accountDetails.User),397 dummyControllerName: dummyControllerName,
191 Password: accountDetails.Password,398 cfgdir: workDir,
192 CACert: one.CACert,399 uuid: uuid,
193 ModelTag: names.NewModelTag(uuid),400 username: accountDetails.User,
194 }401 password: accountDetails.Password,
195402 addresses: one.APIEndpoints,
196 err = writeProcessInfo(envName, &processInfo{403 caCert: []byte(one.CACert),
197 WorkDir: workDir,404 }
198 EndpointAddr: one.APIEndpoints[0],405 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}406}
291407
292// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment408// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -326,12 +442,16 @@
326 return failuresInfo, nil442 return failuresInfo, nil
327}443}
328444
445//===================================================================
446// The fake-juju daemon (started by bootstrap) is found here. It is
447// implemented as a test suite.
448
329type FakeJujuSuite struct {449type FakeJujuSuite struct {
330 jujutesting.JujuConnSuite450 jujutesting.JujuConnSuite
331451
332 instanceCount int452 instanceCount int
333 machineStarted map[string]bool453 machineStarted map[string]bool
334 fifoPath string454 filenames fakejujuFilenames
335 logFile *os.File455 logFile *os.File
336}456}
337457
@@ -398,20 +518,16 @@
398 c.Assert(err, gc.IsNil)518 c.Assert(err, gc.IsNil)
399 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))519 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
400520
401 s.fifoPath = filepath.Join(jujuHome, "fifo")521 s.filenames = newFakeJujuFilenames("", "", jujuHome)
402 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)522 syscall.Mknod(s.filenames.fifoFile(), syscall.S_IFIFO|0666, 0)
403523
404 // Logging524 // Logging
405 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")525 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)526 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
411 c.Assert(err, gc.IsNil)527 c.Assert(err, gc.IsNil)
412528
413 log.SetOutput(s.logFile)529 log.SetOutput(s.logFile)
414 log.Println("Started fake-juju at", jujuHome)530 log.Println("Started fake-juju at ", jujuHome)
415531
416}532}
417533
@@ -423,16 +539,17 @@
423}539}
424540
425func (s *FakeJujuSuite) TestStart(c *gc.C) {541func (s *FakeJujuSuite) TestStart(c *gc.C) {
542 fifoPath := s.filenames.fifoFile()
426 watcher := s.State.Watch()543 watcher := s.State.Watch()
427 go func() {544 go func() {
428 log.Println("Open commands FIFO", s.fifoPath)545 log.Println("Open commands FIFO", fifoPath)
429 fd, err := os.Open(s.fifoPath)546 fd, err := os.Open(fifoPath)
430 if err != nil {547 if err != nil {
431 log.Println("Failed to open commands FIFO")548 log.Println("Failed to open commands FIFO")
432 }549 }
433 c.Assert(err, gc.IsNil)550 c.Assert(err, gc.IsNil)
434 scanner := bufio.NewScanner(fd)551 scanner := bufio.NewScanner(fd)
435 log.Println("Listen for commands on FIFO", s.fifoPath)552 log.Println("Listen for commands on FIFO", fifoPath)
436 scanner.Scan()553 scanner.Scan()
437 log.Println("Stopping fake-juju")554 log.Println("Stopping fake-juju")
438 watcher.Stop()555 watcher.Stop()
439556
=== modified file 'Makefile'
--- Makefile 2016-10-26 14:46:58 +0000
+++ Makefile 2016-10-26 18:45:46 +0000
@@ -14,7 +14,7 @@
14INSTALLDIR = $(DESTDIR)/usr/bin14INSTALLDIR = $(DESTDIR)/usr/bin
15INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION)15INSTALLED = $(INSTALLDIR)/fake-juju-$(JUJU_VERSION)
1616
17$(JUJU_VERSION)/$(JUJU_VERSION):17$(JUJU_VERSION)/$(JUJU_VERSION): $(JUJU_VERSION)/fake-juju.go
18 case $(JUJU_VERSION) in \18 case $(JUJU_VERSION) in \
19 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\19 1.*) $(MAKE) build-common PATH=$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\
20 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\20 2.*) $(MAKE) build-common PATH=/usr/lib/go-$(GO_VERSION)/bin:$$PATH JUJU_VERSION=$(JUJU_VERSION) ;;\
2121
=== modified file 'python/fakejuju/fakejuju.py'
--- python/fakejuju/fakejuju.py 2016-10-18 20:53:54 +0000
+++ python/fakejuju/fakejuju.py 2016-10-26 18:45:46 +0000
@@ -24,15 +24,17 @@
24 return os.path.join(bindir, filename)24 return os.path.join(bindir, filename)
2525
2626
27def set_envvars(envvars, failures_filename=None, logsdir=None):27def set_envvars(envvars, datadir=None, failures_filename=None, logsdir=None):
28 """Return the environment variables with which to run fake-juju.28 """Return the environment variables with which to run fake-juju.
2929
30 @param envvars: The env dict to update.30 @param envvars: The env dict to update.
31 @param datadir: The fake-juju data directory.
31 @param failures_filename: The path to the failures file that32 @param failures_filename: The path to the failures file that
32 fake-juju will use.33 fake-juju will use.
33 @params logsdir: The path to the directory where fake-juju will34 @params logsdir: The path to the directory where fake-juju will
34 write its log files.35 write its log files.
35 """36 """
37 envvars["FAKE_JUJU_DATA_DIR"] = datadir or ""
36 envvars["FAKE_JUJU_FAILURES"] = failures_filename or ""38 envvars["FAKE_JUJU_FAILURES"] = failures_filename or ""
37 envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or ""39 envvars["FAKE_JUJU_LOGS_DIR"] = logsdir or ""
3840
@@ -41,46 +43,47 @@
41 """The fundamental details for fake-juju."""43 """The fundamental details for fake-juju."""
4244
43 @classmethod45 @classmethod
44 def from_version(cls, version, cfgdir,46 def from_version(cls, version, datadir,
45 logsdir=None, failuresdir=None, bindir=None):47 logsdir=None, failuresdir=None, bindir=None):
46 """Return a new instance given the provided information.48 """Return a new instance given the provided information.
4749
48 @param version: The Juju version to fake.50 @param version: The Juju version to fake.
49 @param cfgdir: The "juju home" directory to use.51 @param datadir: The directory in which to store files specific
52 to fake-juju.
50 @param logsdir: The directory where logs will be written.53 @param logsdir: The directory where logs will be written.
51 This defaults to cfgdir.54 This defaults to datadir.
52 @params failuresdir: The directory where failure injection55 @params failuresdir: The directory where failure injection
53 is managed.56 is managed.
54 @param bindir: The directory containing the fake-juju binary.57 @param bindir: The directory containing the fake-juju binary.
55 This defaults to /usr/bin.58 This defaults to /usr/bin.
56 """59 """
57 if logsdir is None:
58 logsdir = cfgdir
59 if failuresdir is None:60 if failuresdir is None:
60 failuresdir = cfgdir61 failuresdir = datadir
61 filename = get_filename(version, bindir=bindir)62 filename = get_filename(version, bindir=bindir)
62 failures = Failures(failuresdir)63 failures = Failures(failuresdir)
63 return cls(filename, version, cfgdir, logsdir, failures)64 return cls(filename, version, datadir, logsdir, failures)
6465
65 def __init__(self, filename, version, cfgdir, logsdir=None, failures=None):66 def __init__(self, filename, version, datadir,
67 logsdir=None, failures=None):
66 """68 """
67 @param filename: The path to the fake-juju binary.69 @param filename: The path to the fake-juju binary.
68 @param version: The Juju version to fake.70 @param version: The Juju version to fake.
69 @param cfgdir: The "juju home" directory to use.71 @param datadir: The directory in which to store files specific
72 to fake-juju.
70 @param logsdir: The directory where logs will be written.73 @param logsdir: The directory where logs will be written.
71 This defaults to cfgdir.74 This defaults to datadir.
72 @param failures: The set of fake-juju failures to use.75 @param failures: The set of fake-juju failures to use.
73 """76 """
74 logsdir = logsdir if logsdir is not None else cfgdir77 logsdir = logsdir if logsdir is not None else datadir
75 if failures is None and cfgdir:78 if failures is None and datadir:
76 failures = Failures(cfgdir)79 failures = Failures(datadir)
7780
78 if not filename:81 if not filename:
79 raise ValueError("missing filename")82 raise ValueError("missing filename")
80 if not version:83 if not version:
81 raise ValueError("missing version")84 raise ValueError("missing version")
82 if not cfgdir:85 if not datadir:
83 raise ValueError("missing cfgdir")86 raise ValueError("missing datadir")
84 if not logsdir:87 if not logsdir:
85 raise ValueError("missing logsdir")88 raise ValueError("missing logsdir")
86 if failures is None:89 if failures is None:
@@ -88,7 +91,7 @@
8891
89 self.filename = filename92 self.filename = filename
90 self.version = version93 self.version = version
91 self.cfgdir = cfgdir94 self.datadir = datadir
92 self.logsdir = logsdir95 self.logsdir = logsdir
93 self.failures = failures96 self.failures = failures
9497
@@ -100,19 +103,19 @@
100 @property103 @property
101 def infofile(self):104 def infofile(self):
102 """The path to fake-juju's data cache."""105 """The path to fake-juju's data cache."""
103 return os.path.join(self.cfgdir, "fakejuju")106 return os.path.join(self.datadir, "fakejuju")
104107
105 @property108 @property
106 def fifo(self):109 def fifo(self):
107 """The path to the fifo file that triggers shutdown."""110 """The path to the fifo file that triggers shutdown."""
108 return os.path.join(self.cfgdir, "fifo")111 return os.path.join(self.datadir, "fifo")
109112
110 @property113 @property
111 def cacertfile(self):114 def cacertfile(self):
112 """The path to the API server's certificate."""115 """The path to the API server's certificate."""
113 return os.path.join(self.cfgdir, "cert.ca")116 return os.path.join(self.datadir, "cert.ca")
114117
115 def cli(self, envvars=None):118 def cli(self, cfgdir, envvars=None):
116 """Return the txjuju.cli.CLI for this fake-juju.119 """Return the txjuju.cli.CLI for this fake-juju.
117120
118 Currently fake-juju supports only the following juju subcommands:121 Currently fake-juju supports only the following juju subcommands:
@@ -124,20 +127,23 @@
124 Note that passwords are always omited, even if requested.127 Note that passwords are always omited, even if requested.
125 * api-endpoints128 * api-endpoints
126 * destroy-environment129 * destroy-environment
130
131 Note that fake-juju ignores local config files.
127 """132 """
128 if envvars is None:133 if envvars is None:
129 envvars = os.environ134 envvars = os.environ
130 envvars = dict(envvars)135 envvars = dict(envvars)
131 set_envvars(envvars, self.failures._filename, self.logsdir)136 set_envvars(
137 envvars, self.datadir, self.failures._filename, self.logsdir)
132 return txjuju.cli.CLI.from_version(138 return txjuju.cli.CLI.from_version(
133 self.filename, self.version, self.cfgdir, envvars)139 self.filename, self.version, cfgdir, envvars)
134140
135 def bootstrap(self, name, admin_secret=None):141 def bootstrap(self, name, cfgdir, admin_secret=None):
136 """Return the CLI and APIInfo after bootstrapping from scratch."""142 """Return the CLI and APIInfo after bootstrapping from scratch."""
137 from . import get_bootstrap_spec143 from . import get_bootstrap_spec
138 spec = get_bootstrap_spec(name, admin_secret)144 spec = get_bootstrap_spec(name, admin_secret)
139 cfgfile = txjuju.prepare_for_bootstrap(spec, self.version, self.cfgdir)145 cfgfile = txjuju.prepare_for_bootstrap(spec, self.version, cfgdir)
140 cli = self.cli()146 cli = self.cli(cfgdir)
141 cli.bootstrap(spec, cfgfile=cfgfile)147 cli.bootstrap(spec, cfgfile=cfgfile)
142 api_info = cli.api_info(spec.name)148 api_info = cli.api_info(spec.name)
143 return cli, api_info149 return cli, api_info
144150
=== modified file 'python/fakejuju/tests/test_fakejuju.py'
--- python/fakejuju/tests/test_fakejuju.py 2016-10-26 17:26:54 +0000
+++ python/fakejuju/tests/test_fakejuju.py 2016-10-26 18:45:46 +0000
@@ -50,9 +50,10 @@
50 def test_all_args(self):50 def test_all_args(self):
51 """set_envvars() works correctly when given all args."""51 """set_envvars() works correctly when given all args."""
52 envvars = {}52 envvars = {}
53 set_envvars(envvars, "/spam/failures", "/eggs/logsdir")53 set_envvars(envvars, "/spam", "/spam/failures", "/eggs/logsdir")
5454
55 self.assertEqual(envvars, {55 self.assertEqual(envvars, {
56 "FAKE_JUJU_DATA_DIR": "/spam",
56 "FAKE_JUJU_FAILURES": "/spam/failures",57 "FAKE_JUJU_FAILURES": "/spam/failures",
57 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",58 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",
58 })59 })
@@ -63,6 +64,7 @@
63 set_envvars(envvars)64 set_envvars(envvars)
6465
65 self.assertEqual(envvars, {66 self.assertEqual(envvars, {
67 "FAKE_JUJU_DATA_DIR": "",
66 "FAKE_JUJU_FAILURES": "",68 "FAKE_JUJU_FAILURES": "",
67 "FAKE_JUJU_LOGS_DIR": "",69 "FAKE_JUJU_LOGS_DIR": "",
68 })70 })
@@ -70,9 +72,10 @@
70 def test_start_empty(self):72 def test_start_empty(self):
71 """set_envvars() sets all values on an empty dict."""73 """set_envvars() sets all values on an empty dict."""
72 envvars = {}74 envvars = {}
73 set_envvars(envvars, "x", "y")75 set_envvars(envvars, "w", "x", "y")
7476
75 self.assertEqual(envvars, {77 self.assertEqual(envvars, {
78 "FAKE_JUJU_DATA_DIR": "w",
76 "FAKE_JUJU_FAILURES": "x",79 "FAKE_JUJU_FAILURES": "x",
77 "FAKE_JUJU_LOGS_DIR": "y",80 "FAKE_JUJU_LOGS_DIR": "y",
78 })81 })
@@ -80,10 +83,11 @@
80 def test_no_collisions(self):83 def test_no_collisions(self):
81 """set_envvars() sets all values when none are set yet."""84 """set_envvars() sets all values when none are set yet."""
82 envvars = {"SPAM": "eggs"}85 envvars = {"SPAM": "eggs"}
83 set_envvars(envvars, "x", "y")86 set_envvars(envvars, "w", "x", "y")
8487
85 self.assertEqual(envvars, {88 self.assertEqual(envvars, {
86 "SPAM": "eggs",89 "SPAM": "eggs",
90 "FAKE_JUJU_DATA_DIR": "w",
87 "FAKE_JUJU_FAILURES": "x",91 "FAKE_JUJU_FAILURES": "x",
88 "FAKE_JUJU_LOGS_DIR": "y",92 "FAKE_JUJU_LOGS_DIR": "y",
89 })93 })
@@ -91,12 +95,14 @@
91 def test_empty_to_nonempty(self):95 def test_empty_to_nonempty(self):
92 """set_envvars() updates empty values."""96 """set_envvars() updates empty values."""
93 envvars = {97 envvars = {
98 "FAKE_JUJU_DATA_DIR": "",
94 "FAKE_JUJU_FAILURES": "",99 "FAKE_JUJU_FAILURES": "",
95 "FAKE_JUJU_LOGS_DIR": "",100 "FAKE_JUJU_LOGS_DIR": "",
96 }101 }
97 set_envvars(envvars, "x", "y")102 set_envvars(envvars, "w", "x", "y")
98103
99 self.assertEqual(envvars, {104 self.assertEqual(envvars, {
105 "FAKE_JUJU_DATA_DIR": "w",
100 "FAKE_JUJU_FAILURES": "x",106 "FAKE_JUJU_FAILURES": "x",
101 "FAKE_JUJU_LOGS_DIR": "y",107 "FAKE_JUJU_LOGS_DIR": "y",
102 })108 })
@@ -104,12 +110,14 @@
104 def test_nonempty_to_nonempty(self):110 def test_nonempty_to_nonempty(self):
105 """set_envvars() overwrites existing values."""111 """set_envvars() overwrites existing values."""
106 envvars = {112 envvars = {
113 "FAKE_JUJU_DATA_DIR": "spam",
107 "FAKE_JUJU_FAILURES": "spam",114 "FAKE_JUJU_FAILURES": "spam",
108 "FAKE_JUJU_LOGS_DIR": "ham",115 "FAKE_JUJU_LOGS_DIR": "ham",
109 }116 }
110 set_envvars(envvars, "x", "y")117 set_envvars(envvars, "w", "x", "y")
111118
112 self.assertEqual(envvars, {119 self.assertEqual(envvars, {
120 "FAKE_JUJU_DATA_DIR": "w",
113 "FAKE_JUJU_FAILURES": "x",121 "FAKE_JUJU_FAILURES": "x",
114 "FAKE_JUJU_LOGS_DIR": "y",122 "FAKE_JUJU_LOGS_DIR": "y",
115 })123 })
@@ -117,12 +125,14 @@
117 def test_nonempty_to_empty(self):125 def test_nonempty_to_empty(self):
118 """set_envvars() with no args "unsets" existing values."""126 """set_envvars() with no args "unsets" existing values."""
119 envvars = {127 envvars = {
128 "FAKE_JUJU_DATA_DIR": "w",
120 "FAKE_JUJU_FAILURES": "x",129 "FAKE_JUJU_FAILURES": "x",
121 "FAKE_JUJU_LOGS_DIR": "y",130 "FAKE_JUJU_LOGS_DIR": "y",
122 }131 }
123 set_envvars(envvars)132 set_envvars(envvars)
124133
125 self.assertEqual(envvars, {134 self.assertEqual(envvars, {
135 "FAKE_JUJU_DATA_DIR": "",
126 "FAKE_JUJU_FAILURES": "",136 "FAKE_JUJU_FAILURES": "",
127 "FAKE_JUJU_LOGS_DIR": "",137 "FAKE_JUJU_LOGS_DIR": "",
128 })138 })
@@ -137,7 +147,7 @@
137147
138 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")148 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")
139 self.assertEqual(juju.version, "1.25.6")149 self.assertEqual(juju.version, "1.25.6")
140 self.assertEqual(juju.cfgdir, "/a/juju/home")150 self.assertEqual(juju.datadir, "/a/juju/home")
141 self.assertEqual(juju.logsdir, "/logs/dir")151 self.assertEqual(juju.logsdir, "/logs/dir")
142 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")152 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")
143153
@@ -147,19 +157,20 @@
147157
148 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")158 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")
149 self.assertEqual(juju.version, "1.25.6")159 self.assertEqual(juju.version, "1.25.6")
150 self.assertEqual(juju.cfgdir, "/my/juju/home")160 self.assertEqual(juju.datadir, "/my/juju/home")
151 self.assertEqual(juju.logsdir, "/my/juju/home")161 self.assertEqual(juju.logsdir, "/my/juju/home")
152 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")162 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
153163
154 def test_full(self):164 def test_full(self):
155 """FakeJuju() works correctly when given all args."""165 """FakeJuju() works correctly when given all args."""
156 cfgdir = "/my/juju/home"166 datadir = "/my/juju/home"
157 failures = Failures(cfgdir)167 failures = Failures(datadir)
158 juju = FakeJuju("/fake-juju", "1.25.6", cfgdir, "/some/logs", failures)168 juju = FakeJuju(
169 "/fake-juju", "1.25.6", datadir, "/some/logs", failures)
159170
160 self.assertEqual(juju.filename, "/fake-juju")171 self.assertEqual(juju.filename, "/fake-juju")
161 self.assertEqual(juju.version, "1.25.6")172 self.assertEqual(juju.version, "1.25.6")
162 self.assertEqual(juju.cfgdir, cfgdir)173 self.assertEqual(juju.datadir, datadir)
163 self.assertEqual(juju.logsdir, "/some/logs")174 self.assertEqual(juju.logsdir, "/some/logs")
164 self.assertIs(juju.failures, failures)175 self.assertIs(juju.failures, failures)
165176
@@ -169,7 +180,7 @@
169180
170 self.assertEqual(juju.filename, "/fake-juju")181 self.assertEqual(juju.filename, "/fake-juju")
171 self.assertEqual(juju.version, "1.25.6")182 self.assertEqual(juju.version, "1.25.6")
172 self.assertEqual(juju.cfgdir, "/my/juju/home")183 self.assertEqual(juju.datadir, "/my/juju/home")
173 self.assertEqual(juju.logsdir, "/my/juju/home")184 self.assertEqual(juju.logsdir, "/my/juju/home")
174 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")185 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
175186
@@ -180,7 +191,7 @@
180 juju_unicode = FakeJuju(191 juju_unicode = FakeJuju(
181 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"/..."))
182193
183 for name in ('filename version cfgdir logsdir'.split()):194 for name in ('filename version datadir logsdir'.split()):
184 self.assertIsInstance(getattr(juju_str, name), str)195 self.assertIsInstance(getattr(juju_str, name), str)
185 self.assertIsInstance(getattr(juju_unicode, name), unicode)196 self.assertIsInstance(getattr(juju_unicode, name), unicode)
186197
@@ -198,8 +209,8 @@
198 with self.assertRaises(ValueError):209 with self.assertRaises(ValueError):
199 FakeJuju("/fake-juju", "", "/my/juju/home")210 FakeJuju("/fake-juju", "", "/my/juju/home")
200211
201 def test_missing_cfgdir(self):212 def test_missing_datadir(self):
202 """FakeJuju() fails if cfgdir is None or empty."""213 """FakeJuju() fails if datadir is None or empty."""
203 with self.assertRaises(ValueError):214 with self.assertRaises(ValueError):
204 FakeJuju("/fake-juju", "1.25.6", None)215 FakeJuju("/fake-juju", "1.25.6", None)
205 with self.assertRaises(ValueError):216 with self.assertRaises(ValueError):
@@ -232,46 +243,48 @@
232 def test_cli_full(self):243 def test_cli_full(self):
233 """FakeJuju.cli() works correctly when given all args."""244 """FakeJuju.cli() works correctly when given all args."""
234 juju = FakeJuju("/fake-juju", "1.25.6", "/x")245 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
235 cli = juju.cli({"SPAM": "eggs"})246 cli = juju.cli("/y", {"SPAM": "eggs"})
236247
237 self.assertEqual(248 self.assertEqual(
238 cli._exe,249 cli._exe,
239 Executable("/fake-juju", {250 Executable("/fake-juju", {
240 "SPAM": "eggs",251 "SPAM": "eggs",
252 "FAKE_JUJU_DATA_DIR": "/x",
241 "FAKE_JUJU_FAILURES": "/x/juju-failures",253 "FAKE_JUJU_FAILURES": "/x/juju-failures",
242 "FAKE_JUJU_LOGS_DIR": "/x",254 "FAKE_JUJU_LOGS_DIR": "/x",
243 "JUJU_HOME": "/x",255 "JUJU_HOME": "/y",
244 }),256 }),
245 )257 )
246258
247 def test_cli_minimal(self):259 def test_cli_minimal(self):
248 """FakeJuju.cli() works correctly when given minimal args."""260 """FakeJuju.cli() works correctly when given minimal args."""
249 juju = FakeJuju("/fake-juju", "1.25.6", "/x")261 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
250 cli = juju.cli()262 cli = juju.cli("/y")
251263
252 self.assertEqual(264 self.assertEqual(
253 cli._exe,265 cli._exe,
254 Executable("/fake-juju", dict(os.environ, **{266 Executable("/fake-juju", dict(os.environ, **{
267 "FAKE_JUJU_DATA_DIR": "/x",
255 "FAKE_JUJU_FAILURES": "/x/juju-failures",268 "FAKE_JUJU_FAILURES": "/x/juju-failures",
256 "FAKE_JUJU_LOGS_DIR": "/x",269 "FAKE_JUJU_LOGS_DIR": "/x",
257 "JUJU_HOME": "/x",270 "JUJU_HOME": "/y",
258 })),271 })),
259 )272 )
260273
261 def test_cli_juju1(self):274 def test_cli_juju1(self):
262 """FakeJuju.cli() works correctly for Juju 1.x."""275 """FakeJuju.cli() works correctly for Juju 1.x."""
263 juju = FakeJuju.from_version("1.25.6", "/x")276 juju = FakeJuju.from_version("1.25.6", "/x")
264 cli = juju.cli()277 cli = juju.cli("/y")
265278
266 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/x")279 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/y")
267 self.assertIsInstance(cli._juju, _juju1.CLIHooks)280 self.assertIsInstance(cli._juju, _juju1.CLIHooks)
268281
269 def test_cli_juju2(self):282 def test_cli_juju2(self):
270 """FakeJuju.cli() works correctly for Juju 2.x."""283 """FakeJuju.cli() works correctly for Juju 2.x."""
271 juju = FakeJuju.from_version("2.0.0", "/x")284 juju = FakeJuju.from_version("2.0.0", "/x")
272 cli = juju.cli()285 cli = juju.cli("/y")
273286
274 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/x")287 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/y")
275 self.assertIsInstance(cli._juju, _juju2.CLIHooks)288 self.assertIsInstance(cli._juju, _juju2.CLIHooks)
276289
277 def test_bootstrap(self):290 def test_bootstrap(self):
@@ -283,21 +296,26 @@
283 model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d',296 model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d',
284 )297 )
285 version = "1.25.6"298 version = "1.25.6"
286 with tempdir() as datadir:299 with tempdir() as testdir:
287 cfgdir = os.path.join(datadir, ".juju")300 bindir = os.path.join(testdir, "bin")
301 datadir = os.path.join(testdir, "fakejuju")
302 cfgdir = os.path.join(testdir, ".juju")
288303
289 logfilename = write_fakejuju_script(304 logfilename = write_fakejuju_script(
290 version, datadir, cfgdir, expected)305 version, bindir, datadir, cfgdir, expected)
291 fakejuju = FakeJuju.from_version(version, cfgdir, bindir=datadir)306 fakejuju = FakeJuju.from_version(version, cfgdir, bindir=bindir)
292307
293 # Make the calls.308 # Make the calls.
294 cli, api_info = fakejuju.bootstrap("spam", "secret")309 cli, api_info = fakejuju.bootstrap("spam", cfgdir, "secret")
295 cli.destroy_controller()310 cli.destroy_controller()
296311
297 with open(logfilename) as logfile:312 with open(logfilename) as logfile:
298 calls = [line.strip() for line in logfile]313 calls = [line.strip() for line in logfile]
299314 files = []
300 files = os.listdir(cfgdir)315 files.extend(os.path.join(os.path.basename(datadir), name)
316 for name in os.listdir(datadir))
317 files.extend(os.path.join(os.path.basename(cfgdir), name)
318 for name in os.listdir(cfgdir))
301 with open(os.path.join(cfgdir, "environments.yaml")) as envfile:319 with open(os.path.join(cfgdir, "environments.yaml")) as envfile:
302 data = envfile.read()320 data = envfile.read()
303321
@@ -317,11 +335,12 @@
317 "destroy-environment",335 "destroy-environment",
318 ])336 ])
319 self.assertItemsEqual(files, [337 self.assertItemsEqual(files, [
320 'cert.ca',338 '.juju/environments',
321 'environments',339 '.juju/environments.yaml',
322 'environments.yaml',340 'fakejuju/cert.ca',
323 'fake-juju.log',341 'fakejuju/fake-juju.log',
324 'fakejuju',342 'fakejuju/fakejuju',
343 'fakejuju/fifo',
325 ])344 ])
326 self.assertEqual(yaml.load(data), {345 self.assertEqual(yaml.load(data), {
327 "environments": {346 "environments": {
@@ -344,7 +363,10 @@
344 logfile.write(" ".join(sys.argv) + "\\n")363 logfile.write(" ".join(sys.argv) + "\\n")
345364
346if sys.argv[1] == "bootstrap":365if sys.argv[1] == "bootstrap":
347 for filename in ("cert.ca", "environments", "fake-juju.log", "fakejuju"):366 for filename in ("cert.ca", "fake-juju.log", "fakejuju", "fifo"):
367 with open(os.path.join("{datadir}", filename), "w"):
368 pass # Touch the file.
369 for filename in ("environments",):
348 with open(os.path.join("{cfgdir}", filename), "w"):370 with open(os.path.join("{cfgdir}", filename), "w"):
349 pass # Touch the file.371 pass # Touch the file.
350elif sys.argv[1] in ("api-info", "show-controller"):372elif sys.argv[1] in ("api-info", "show-controller"):
@@ -353,7 +375,7 @@
353"""375"""
354376
355377
356def write_fakejuju_script(version, bindir, cfgdir, api_info):378def write_fakejuju_script(version, bindir, datadir, cfgdir, api_info):
357 if version.startswith("1."):379 if version.startswith("1."):
358 raw_api_info = {380 raw_api_info = {
359 "state-servers": api_info.endpoints,381 "state-servers": api_info.endpoints,
@@ -383,11 +405,14 @@
383405
384 logfile = os.path.join(bindir, "calls.log")406 logfile = os.path.join(bindir, "calls.log")
385 script = FAKE_JUJU_SCRIPT.format(407 script = FAKE_JUJU_SCRIPT.format(
386 cfgdir=cfgdir, logfile=logfile, output=output)408 datadir=datadir, cfgdir=cfgdir, logfile=logfile, output=output)
387 filename = get_filename(version, bindir)409 filename = get_filename(version, bindir)
410 os.makedirs(os.path.dirname(filename))
388 with open(filename, "w") as scriptfile:411 with open(filename, "w") as scriptfile:
389 scriptfile.write(script)412 scriptfile.write(script)
390 os.chmod(filename, 0o755)413 os.chmod(filename, 0o755)
414 os.makedirs(datadir)
415
391 return logfile416 return logfile
392417
393418

Subscribers

People subscribed via source and target branches

to all changes: