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

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

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

Description of the change

Fix a couple of broken behaviors for testing.

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

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

Merge from parent.

61. By Eric Snow

Fix a rename.

62. By Eric Snow

Merge from parent.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== 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-20 21:22:41 +0000
@@ -50,39 +50,36 @@
50 coretesting.MgoTestPackage(t)50 coretesting.MgoTestPackage(t)
51}51}
5252
53type processInfo struct {
54 Username string
55 WorkDir string
56 EndpointAddr string
57 Uuid string
58 CACert string
59}
60
61func handleCommand(command string) error {53func handleCommand(command string) error {
54 filenames := newFakeJujuFilenames("", "", "")
62 if command == "bootstrap" {55 if command == "bootstrap" {
63 return bootstrap()56 return bootstrap(filenames)
64 }57 }
65 if command == "api-endpoints" {58 if command == "api-endpoints" {
66 return apiEndpoints()59 return apiEndpoints(filenames)
67 }60 }
68 if command == "api-info" {61 if command == "api-info" {
69 return apiInfo()62 return apiInfo(filenames)
70 }63 }
71 if command == "destroy-environment" {64 if command == "destroy-environment" {
72 return destroyEnvironment()65 return destroyEnvironment(filenames)
73 }66 }
74 return errors.New("command not found")67 return errors.New("command not found")
75}68}
7669
77func bootstrap() error {70func bootstrap(filenames fakejujuFilenames) error {
71 if err := filenames.ensureDirsExist(); err != nil {
72 return err
73 }
78 envName, config, err := environmentNameAndConfig()74 envName, config, err := environmentNameAndConfig()
79 if err != nil {75 if err != nil {
80 return err76 return err
81 }77 }
78 password := config.AdminSecret()
79
82 command := exec.Command(os.Args[0])80 command := exec.Command(os.Args[0])
83 command.Env = os.Environ()81 command.Env = os.Environ()
84 command.Env = append(82 command.Env = append(command.Env, "ADMIN_PASSWORD="+password)
85 command.Env, "ADMIN_PASSWORD="+config.AdminSecret())
86 defaultSeries, _ := config.DefaultSeries()83 defaultSeries, _ := config.DefaultSeries()
87 command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries)84 command.Env = append(command.Env, "DEFAULT_SERIES="+defaultSeries)
88 stdout, err := command.StdoutPipe()85 stdout, err := command.StdoutPipe()
@@ -90,10 +87,23 @@
90 return err87 return err
91 }88 }
92 command.Start()89 command.Start()
93 apiInfo, err := parseApiInfo(envName, stdout)90
91 result, err := parseApiInfo(stdout)
94 if err != nil {92 if err != nil {
95 return err93 return err
96 }94 }
95 // Get the API info before changing it. The new values might
96 // not work yet.
97 apiInfo := result.apiInfo()
98 // We actually want to report the API user we added in SetUpTest().
99 result.username = "admin"
100 if password != "" {
101 result.password = password
102 }
103 if err := result.apply(filenames, envName); err != nil {
104 return err
105 }
106
97 dialOpts := api.DialOpts{107 dialOpts := api.DialOpts{
98 DialAddressInterval: 50 * time.Millisecond,108 DialAddressInterval: 50 * time.Millisecond,
99 Timeout: 5 * time.Second,109 Timeout: 5 * time.Second,
@@ -123,8 +133,8 @@
123 return errors.New("invalid delta")133 return errors.New("invalid delta")
124}134}
125135
126func apiEndpoints() error {136func apiEndpoints(filenames fakejujuFilenames) error {
127 info, err := readProcessInfo()137 info, err := readProcessInfo(filenames)
128 if err != nil {138 if err != nil {
129 return err139 return err
130 }140 }
@@ -132,23 +142,22 @@
132 return nil142 return nil
133}143}
134144
135func apiInfo() error {145func apiInfo(filenames fakejujuFilenames) error {
136 info, err := readProcessInfo()146 info, err := readProcessInfo(filenames)
137 if err != nil {147 if err != nil {
138 return err148 return err
139 }149 }
140 username := strings.Replace(string(info.Username), "dummy-", "", 1)150 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 nil151 return nil
143}152}
144153
145func destroyEnvironment() error {154func destroyEnvironment(filenames fakejujuFilenames) error {
146 info, err := readProcessInfo()155 info, err := readProcessInfo(filenames)
147 if err != nil {156 if err != nil {
148 return err157 return err
149 }158 }
150 fifoPath := filepath.Join(info.WorkDir, "fifo")159 filenames = newFakeJujuFilenames("", "", info.WorkDir)
151 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)160 fd, err := os.OpenFile(filenames.fifo(), os.O_APPEND|os.O_WRONLY, 0600)
152 if err != nil {161 if err != nil {
153 return err162 return err
154 }163 }
@@ -176,93 +185,206 @@
176 return envName, config, nil185 return envName, config, nil
177}186}
178187
179func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {188type processInfo struct {
189 Username string
190 Password string
191 WorkDir string
192 EndpointAddr string
193 Uuid string
194 CACert []byte
195}
196
197func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
198 infoPath := filenames.info()
199 data, err := ioutil.ReadFile(infoPath)
200 if err != nil {
201 return nil, err
202 }
203 info := &processInfo{}
204 err = goyaml.Unmarshal(data, info)
205 if err != nil {
206 return nil, err
207 }
208 return info, nil
209}
210
211func (info processInfo) write(infoPath string) error {
212 data, _ := goyaml.Marshal(&info)
213 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
214 return err
215 }
216 return nil
217}
218
219type fakejujuFilenames struct {
220 datadir string
221 logsdir string
222}
223
224func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
225 if datadir == "" {
226 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
227 if datadir == "" {
228 if jujucfgdir == "" {
229 jujucfgdir = os.Getenv("JUJU_HOME")
230 }
231 datadir = jujucfgdir
232 }
233 }
234 if logsdir == "" {
235 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
236 if logsdir == "" {
237 logsdir = datadir
238 }
239 }
240 return fakejujuFilenames{datadir, logsdir}
241}
242
243func (fj fakejujuFilenames) ensureDirsExist() error {
244 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
245 return err
246 }
247 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
248 return err
249 }
250 return nil
251}
252
253func (fj fakejujuFilenames) info() string {
254 return filepath.Join(fj.datadir, "fakejuju")
255}
256
257func (fj fakejujuFilenames) logs() string {
258 return filepath.Join(fj.logsdir, "fake-juju.log")
259}
260
261func (fj fakejujuFilenames) fifo() string {
262 return filepath.Join(fj.datadir, "fifo")
263}
264
265func (fj fakejujuFilenames) cacert() string {
266 return filepath.Join(fj.datadir, "cert.ca")
267}
268
269type bootstrapResult struct {
270 dummyEnvName string
271 cfgdir string
272 uuid string
273 username string
274 password string
275 addresses []string
276 caCert []byte
277}
278
279func (br bootstrapResult) apiInfo() *api.Info {
280 return &api.Info{
281 Addrs: br.addresses,
282 Tag: names.NewLocalUserTag(br.username),
283 Password: br.password,
284 CACert: string(br.caCert),
285 EnvironTag: names.NewEnvironTag(br.uuid),
286 }
287}
288
289func (br bootstrapResult) fakeJujuInfo() *processInfo {
290 return &processInfo{
291 Username: br.username,
292 Password: br.password,
293 WorkDir: br.cfgdir,
294 EndpointAddr: br.addresses[0],
295 Uuid: br.uuid,
296 CACert: br.caCert,
297 }
298}
299
300func (br bootstrapResult) logsSymlink(target string) (string, string) {
301 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" || os.Getenv("FAKE_JUJU_DATA_DIR") != "" {
302 return "", ""
303 }
304
305 filenames := newFakeJujuFilenames("", "", br.cfgdir)
306 source := filenames.logs()
307 return source, target
308}
309
310func (br bootstrapResult) jenvSymlink(jujuHome, envName string) (string, string) {
311 if jujuHome == "" || envName == "" {
312 return "", ""
313 }
314
315 source := filepath.Join(br.cfgdir, "environments", br.dummyEnvName+".jenv")
316 target := filepath.Join(jujuHome, "environments", envName+".jenv")
317 return source, target
318}
319
320func (br bootstrapResult) apply(filenames fakejujuFilenames, envName string) error {
321 if err := br.fakeJujuInfo().write(filenames.info()); err != nil {
322 return err
323 }
324
325 logsSource, logsTarget := br.logsSymlink(filenames.logs())
326 if logsSource != "" && logsTarget != "" {
327 if err := os.Symlink(logsSource, logsTarget); err != nil {
328 return err
329 }
330 }
331
332 jenvSource, jenvTarget := br.jenvSymlink(os.Getenv("JUJU_HOME"), envName)
333 if jenvSource != "" && jenvTarget != "" {
334 if err := os.MkdirAll(filepath.Dir(jenvTarget), 0755); err != nil {
335 return err
336 }
337 if err := os.Symlink(jenvSource, jenvTarget); err != nil {
338 return err
339 }
340 }
341
342 if err := ioutil.WriteFile(filenames.cacert(), br.caCert, 0644); err != nil {
343 return err
344 }
345
346 return nil
347}
348
349// See github.com/juju/juju/blob/juju/testing/conn.go.
350const dummyEnvName = "dummyenv"
351
352func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
180 buffer := bufio.NewReader(stdout)353 buffer := bufio.NewReader(stdout)
354
181 line, _, err := buffer.ReadLine()355 line, _, err := buffer.ReadLine()
182 if err != nil {356 if err != nil {
183 return nil, err357 return nil, err
184 }358 }
185 uuid := string(line)359 uuid := string(line)
186 environTag := names.NewEnvironTag(uuid)360
187 line, _, err = buffer.ReadLine()361 line, _, err = buffer.ReadLine()
188 if err != nil {362 if err != nil {
189 return nil, err363 return nil, err
190 }364 }
191 workDir := string(line)365 workDir := string(line)
366
192 store, err := configstore.NewDisk(workDir)367 store, err := configstore.NewDisk(workDir)
193 if err != nil {368 if err != nil {
194 return nil, err369 return nil, err
195 }370 }
196 info, err := store.ReadInfo("dummyenv")371 info, err := store.ReadInfo(dummyEnvName)
197 if err != nil {372 if err != nil {
198 return nil, err373 return nil, err
199 }374 }
375
200 credentials := info.APICredentials()376 credentials := info.APICredentials()
201 endpoint := info.APIEndpoint()377 endpoint := info.APIEndpoint()
202 addresses := endpoint.Addresses378 result := &bootstrapResult{
203 apiInfo := &api.Info{379 dummyEnvName: dummyEnvName,
204 Addrs: addresses,380 cfgdir: workDir,
205 Tag: names.NewLocalUserTag(credentials.User),381 uuid: uuid,
206 Password: credentials.Password,382 username: credentials.User,
207 CACert: endpoint.CACert,383 password: credentials.Password,
208 EnvironTag: environTag,384 addresses: endpoint.Addresses,
209 }385 caCert: []byte(endpoint.CACert),
210 err = writeProcessInfo(envName, &processInfo{386 }
211 Username: credentials.User,387 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}388}
267389
268// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment390// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -307,7 +429,7 @@
307429
308 instanceCount int430 instanceCount int
309 machineStarted map[string]bool431 machineStarted map[string]bool
310 fifoPath string432 filenames fakejujuFilenames
311 logFile *os.File433 logFile *os.File
312}434}
313435
@@ -359,7 +481,6 @@
359 c.Assert(err, gc.IsNil)481 c.Assert(err, gc.IsNil)
360482
361 apiInfo := s.APIInfo(c)483 apiInfo := s.APIInfo(c)
362 //fmt.Println(apiInfo.Addrs[0])
363 jujuHome := osenv.JujuHome()484 jujuHome := osenv.JujuHome()
364 // IMPORTANT: don't remove this logging because it's used by the485 // IMPORTANT: don't remove this logging because it's used by the
365 // bootstrap command.486 // bootstrap command.
@@ -374,15 +495,11 @@
374 c.Assert(err, gc.IsNil)495 c.Assert(err, gc.IsNil)
375 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))496 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
376497
377 s.fifoPath = filepath.Join(jujuHome, "fifo")498 s.filenames = newFakeJujuFilenames("", "", jujuHome)
378 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)499 syscall.Mknod(s.filenames.fifo(), syscall.S_IFIFO|0666, 0)
379500
380 // Logging501 // Logging
381 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")502 logPath := s.filenames.logs()
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)503 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
387 c.Assert(err, gc.IsNil)504 c.Assert(err, gc.IsNil)
388505
@@ -402,16 +519,30 @@
402}519}
403520
404func (s *FakeJujuSuite) TestStart(c *gc.C) {521func (s *FakeJujuSuite) TestStart(c *gc.C) {
522 fifoPath := s.filenames.fifo()
405 watcher := s.State.Watch()523 watcher := s.State.Watch()
406 go func() {524 go func() {
407 log.Println("Open commands FIFO", s.fifoPath)525 log.Println("Open commands FIFO", fifoPath)
408 fd, err := os.Open(s.fifoPath)526 fd, err := os.Open(fifoPath)
409 if err != nil {527 if err != nil {
410 log.Println("Failed to open commands FIFO")528 log.Println("Failed to open commands FIFO")
411 }529 }
412 c.Assert(err, gc.IsNil)530 c.Assert(err, gc.IsNil)
531 defer func() {
532 if err := fd.Close(); err != nil {
533 c.Logf("failed closing FIFO file: %s", err)
534 }
535 // Mark the controller as destroyed by renaming some files.
536 if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil {
537 c.Logf("failed renaming FIFO file: %s", err)
538 }
539 infofile := s.filenames.info()
540 if err := os.Rename(infofile, infofile+".destroyed"); err != nil {
541 c.Logf("failed renaming info file: %s", err)
542 }
543 }()
413 scanner := bufio.NewScanner(fd)544 scanner := bufio.NewScanner(fd)
414 log.Println("Listen for commands on FIFO", s.fifoPath)545 log.Println("Listen for commands on FIFO", fifoPath)
415 scanner.Scan()546 scanner.Scan()
416 log.Println("Stopping fake-juju")547 log.Println("Stopping fake-juju")
417 watcher.Stop()548 watcher.Stop()
418549
=== 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-20 21:22:41 +0000
@@ -51,33 +51,31 @@
51 coretesting.MgoTestPackage(t)51 coretesting.MgoTestPackage(t)
52}52}
5353
54type processInfo struct {
55 WorkDir string
56 EndpointAddr string
57 Uuid string
58 CACert string
59}
60
61func handleCommand(command string) error {54func handleCommand(command string) error {
55 filenames := newFakeJujuFilenames("", "", "")
62 if command == "bootstrap" {56 if command == "bootstrap" {
63 return bootstrap()57 return bootstrap(filenames)
64 }58 }
65 if command == "show-controller" {59 if command == "show-controller" {
66 return apiInfo()60 return apiInfo(filenames)
67 }61 }
68 if command == "destroy-controller" {62 if command == "destroy-controller" {
69 return destroyEnvironment()63 return destroyController(filenames)
70 }64 }
71 return errors.New("command not found")65 return errors.New("command not found")
72}66}
7367
74func bootstrap() error {68func bootstrap(filenames fakejujuFilenames) error {
75 argc := len(os.Args)69 argc := len(os.Args)
76 if argc < 4 {70 if argc < 4 {
77 return errors.New(71 return errors.New(
78 "error: controller name and cloud name are required")72 "error: controller name and cloud name are required")
79 }73 }
80 envName := os.Args[argc-2]74 if err := filenames.ensureDirsExist(); err != nil {
75 return err
76 }
77 // XXX Swap the 2 args for juju-2.0-final.
78 controllerName := os.Args[argc-2]
81 command := exec.Command(os.Args[0])79 command := exec.Command(os.Args[0])
82 command.Env = os.Environ()80 command.Env = os.Environ()
83 command.Env = append(81 command.Env = append(
@@ -89,10 +87,16 @@
89 return err87 return err
90 }88 }
91 command.Start()89 command.Start()
92 apiInfo, err := parseApiInfo(envName, stdout)90
91 result, err := parseApiInfo(stdout)
93 if err != nil {92 if err != nil {
94 return err93 return err
95 }94 }
95 if err := result.apply(filenames, controllerName); err != nil {
96 return err
97 }
98 apiInfo := result.apiInfo()
99
96 dialOpts := api.DialOpts{100 dialOpts := api.DialOpts{
97 DialAddressInterval: 50 * time.Millisecond,101 DialAddressInterval: 50 * time.Millisecond,
98 Timeout: 5 * time.Second,102 Timeout: 5 * time.Second,
@@ -122,8 +126,8 @@
122 return errors.New("invalid delta")126 return errors.New("invalid delta")
123}127}
124128
125func apiInfo() error {129func apiInfo(filenames fakejujuFilenames) error {
126 info, err := readProcessInfo()130 info, err := readProcessInfo(filenames)
127 if err != nil {131 if err != nil {
128 return err132 return err
129 }133 }
@@ -140,13 +144,13 @@
140 return nil144 return nil
141}145}
142146
143func destroyEnvironment() error {147func destroyController(filenames fakejujuFilenames) error {
144 info, err := readProcessInfo()148 info, err := readProcessInfo(filenames)
145 if err != nil {149 if err != nil {
146 return err150 return err
147 }151 }
148 fifoPath := filepath.Join(info.WorkDir, "fifo")152 filenames = newFakeJujuFilenames("", "", info.WorkDir)
149 fd, err := os.OpenFile(fifoPath, os.O_APPEND|os.O_WRONLY, 0600)153 fd, err := os.OpenFile(filenames.fifo(), os.O_APPEND|os.O_WRONLY, 0600)
150 if err != nil {154 if err != nil {
151 return err155 return err
152 }156 }
@@ -158,13 +162,187 @@
158 return nil162 return nil
159}163}
160164
161func parseApiInfo(envName string, stdout io.ReadCloser) (*api.Info, error) {165type processInfo struct {
166 WorkDir string
167 EndpointAddr string
168 Uuid string
169 CACert []byte
170}
171
172func readProcessInfo(filenames fakejujuFilenames) (*processInfo, error) {
173 infoPath := filenames.info()
174 data, err := ioutil.ReadFile(infoPath)
175 if err != nil {
176 return nil, err
177 }
178 info := &processInfo{}
179 err = goyaml.Unmarshal(data, info)
180 if err != nil {
181 return nil, err
182 }
183 return info, nil
184}
185
186func (info processInfo) write(infoPath string) error {
187 data, _ := goyaml.Marshal(&info)
188 if err := ioutil.WriteFile(infoPath, data, 0644); err != nil {
189 return err
190 }
191 return nil
192}
193
194type fakejujuFilenames struct {
195 datadir string
196 logsdir string
197}
198
199func newFakeJujuFilenames(datadir, logsdir, jujucfgdir string) fakejujuFilenames {
200 if datadir == "" {
201 datadir = os.Getenv("FAKE_JUJU_DATA_DIR")
202 if datadir == "" {
203 if jujucfgdir == "" {
204 jujucfgdir = os.Getenv("JUJU_DATA")
205 }
206 datadir = jujucfgdir
207 }
208 }
209 if logsdir == "" {
210 logsdir = os.Getenv("FAKE_JUJU_LOGS_DIR")
211 if logsdir == "" {
212 logsdir = datadir
213 }
214 }
215 return fakejujuFilenames{datadir, logsdir}
216}
217
218func (fj fakejujuFilenames) ensureDirsExist() error {
219 if err := os.MkdirAll(fj.datadir, 0755); err != nil {
220 return err
221 }
222 if err := os.MkdirAll(fj.logsdir, 0755); err != nil {
223 return err
224 }
225 return nil
226}
227
228func (fj fakejujuFilenames) info() string {
229 return filepath.Join(fj.datadir, "fakejuju")
230}
231
232func (fj fakejujuFilenames) logs() string {
233 return filepath.Join(fj.logsdir, "fake-juju.log")
234}
235
236func (fj fakejujuFilenames) fifo() string {
237 return filepath.Join(fj.datadir, "fifo")
238}
239
240func (fj fakejujuFilenames) cacert() string {
241 return filepath.Join(fj.datadir, "cert.ca")
242}
243
244type bootstrapResult struct {
245 dummyControllerName string
246 cfgdir string
247 uuid string
248 username string
249 password string
250 addresses []string
251 caCert []byte
252}
253
254func (br bootstrapResult) apiInfo() *api.Info {
255 return &api.Info{
256 Addrs: br.addresses,
257 Tag: names.NewUserTag(br.username),
258 Password: br.password,
259 CACert: string(br.caCert),
260 ModelTag: names.NewModelTag(br.uuid),
261 }
262}
263
264func (br bootstrapResult) fakeJujuInfo() *processInfo {
265 return &processInfo{
266 WorkDir: br.cfgdir,
267 EndpointAddr: br.addresses[0],
268 Uuid: br.uuid,
269 CACert: br.caCert,
270 }
271}
272
273func (br bootstrapResult) logsSymlink(target string) (string, string) {
274 if os.Getenv("FAKE_JUJU_LOGS_DIR") != "" {
275 return "", ""
276 }
277
278 filenames := newFakeJujuFilenames("", "", br.cfgdir)
279 source := filenames.logs()
280 return source, target
281}
282
283func (br bootstrapResult) apply(filenames fakejujuFilenames, controllerName string) error {
284 if err := br.fakeJujuInfo().write(filenames.info()); err != nil {
285 return err
286 }
287
288 logsSource, logsTarget := br.logsSymlink(filenames.logs())
289 if logsSource != "" && logsTarget != "" {
290 if err := os.Symlink(logsSource, logsTarget); err != nil {
291 return err
292 }
293 }
294
295 if err := br.copyConfig(os.Getenv("JUJU_DATA"), controllerName); err != nil {
296 return err
297 }
298
299 if err := ioutil.WriteFile(filenames.cacert(), br.caCert, 0644); err != nil {
300 return err
301 }
302
303 return nil
304}
305
306func (br bootstrapResult) copyConfig(cfgdir, controllerName string) error {
307 for _, name := range []string{"controllers.yaml", "models.yaml", "accounts.yaml"} {
308 source := filepath.Join(br.cfgdir, name)
309 target := filepath.Join(cfgdir, name)
310
311 input, err := ioutil.ReadFile(source)
312 if err != nil {
313 return err
314 }
315 // Generated configuration by test fixtures has the controller name
316 // hard-coded to "kontroll". A simple replace should fix this for
317 // clients using this config and expecting a specific controller
318 // name.
319 output := strings.Replace(string(input), dummyControllerName, controllerName, -1)
320 err = ioutil.WriteFile(target, []byte(output), 0644)
321 if err != nil {
322 return err
323 }
324 }
325
326 current := filepath.Join(cfgdir, "current-controller")
327 if err := ioutil.WriteFile(current, []byte(controllerName), 0644); err != nil {
328 return err
329 }
330
331 return nil
332}
333
334// See github.com/juju/juju/blob/juju/testing/conn.go.
335const dummyControllerName = "kontroll"
336
337func parseApiInfo(stdout io.ReadCloser) (*bootstrapResult, error) {
162 buffer := bufio.NewReader(stdout)338 buffer := bufio.NewReader(stdout)
339
163 line, _, err := buffer.ReadLine()340 line, _, err := buffer.ReadLine()
164 if err != nil {341 if err != nil {
165 return nil, err342 return nil, err
166 }343 }
167 uuid := string(line)344 uuid := string(line)
345
168 line, _, err = buffer.ReadLine()346 line, _, err = buffer.ReadLine()
169 if err != nil {347 if err != nil {
170 return nil, err348 return nil, err
@@ -175,8 +353,8 @@
175 store := jujuclient.NewFileClientStore()353 store := jujuclient.NewFileClientStore()
176 // hard-coded value in juju testing354 // hard-coded value in juju testing
177 // This will be replaced in JUJU_DATA copy of the juju client config.355 // This will be replaced in JUJU_DATA copy of the juju client config.
178 currentController := "kontroll"356 currentController := dummyControllerName
179 one, err := store.ControllerByName("kontroll")357 one, err := store.ControllerByName(currentController)
180 if err != nil {358 if err != nil {
181 return nil, err359 return nil, err
182 }360 }
@@ -185,108 +363,17 @@
185 if err != nil {363 if err != nil {
186 return nil, err364 return nil, err
187 }365 }
188 apiInfo := &api.Info{366
189 Addrs: one.APIEndpoints,367 result := &bootstrapResult{
190 Tag: names.NewUserTag(accountDetails.User),368 dummyControllerName: dummyControllerName,
191 Password: accountDetails.Password,369 cfgdir: workDir,
192 CACert: one.CACert,370 uuid: uuid,
193 ModelTag: names.NewModelTag(uuid),371 username: accountDetails.User,
194 }372 password: accountDetails.Password,
195373 addresses: one.APIEndpoints,
196 err = writeProcessInfo(envName, &processInfo{374 caCert: []byte(one.CACert),
197 WorkDir: workDir,375 }
198 EndpointAddr: one.APIEndpoints[0],376 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}377}
291378
292// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment379// Read the failures info file pointed by the FAKE_JUJU_FAILURES environment
@@ -331,7 +418,7 @@
331418
332 instanceCount int419 instanceCount int
333 machineStarted map[string]bool420 machineStarted map[string]bool
334 fifoPath string421 filenames fakejujuFilenames
335 logFile *os.File422 logFile *os.File
336}423}
337424
@@ -398,20 +485,16 @@
398 c.Assert(err, gc.IsNil)485 c.Assert(err, gc.IsNil)
399 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))486 os.Setenv("PATH", binPath+":"+os.Getenv("PATH"))
400487
401 s.fifoPath = filepath.Join(jujuHome, "fifo")488 s.filenames = newFakeJujuFilenames("", "", jujuHome)
402 syscall.Mknod(s.fifoPath, syscall.S_IFIFO|0666, 0)489 syscall.Mknod(s.filenames.fifo(), syscall.S_IFIFO|0666, 0)
403490
404 // Logging491 // Logging
405 logsDir := os.Getenv("FAKE_JUJU_LOGS_DIR")492 logPath := s.filenames.logs()
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)493 s.logFile, err = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
411 c.Assert(err, gc.IsNil)494 c.Assert(err, gc.IsNil)
412495
413 log.SetOutput(s.logFile)496 log.SetOutput(s.logFile)
414 log.Println("Started fake-juju at", jujuHome)497 log.Println("Started fake-juju at ", jujuHome)
415498
416}499}
417500
@@ -423,16 +506,30 @@
423}506}
424507
425func (s *FakeJujuSuite) TestStart(c *gc.C) {508func (s *FakeJujuSuite) TestStart(c *gc.C) {
509 fifoPath := s.filenames.fifo()
426 watcher := s.State.Watch()510 watcher := s.State.Watch()
427 go func() {511 go func() {
428 log.Println("Open commands FIFO", s.fifoPath)512 log.Println("Open commands FIFO", fifoPath)
429 fd, err := os.Open(s.fifoPath)513 fd, err := os.Open(fifoPath)
430 if err != nil {514 if err != nil {
431 log.Println("Failed to open commands FIFO")515 log.Println("Failed to open commands FIFO")
432 }516 }
433 c.Assert(err, gc.IsNil)517 c.Assert(err, gc.IsNil)
518 defer func() {
519 if err := fd.Close(); err != nil {
520 c.Logf("failed closing FIFO file: %s", err)
521 }
522 // Mark the controller as destroyed by renaming some files.
523 if err := os.Rename(fifoPath, fifoPath+".destroyed"); err != nil {
524 c.Logf("failed renaming FIFO file: %s", err)
525 }
526 infofile := s.filenames.info()
527 if err := os.Rename(infofile, infofile+".destroyed"); err != nil {
528 c.Logf("failed renaming info file: %s", err)
529 }
530 }()
434 scanner := bufio.NewScanner(fd)531 scanner := bufio.NewScanner(fd)
435 log.Println("Listen for commands on FIFO", s.fifoPath)532 log.Println("Listen for commands on FIFO", fifoPath)
436 scanner.Scan()533 scanner.Scan()
437 log.Println("Stopping fake-juju")534 log.Println("Stopping fake-juju")
438 watcher.Stop()535 watcher.Stop()
439536
=== modified file 'Makefile'
--- Makefile 2016-09-20 18:26:47 +0000
+++ Makefile 2016-10-20 21:22:41 +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) ;;\
1818
=== modified file 'python/Makefile'
--- python/Makefile 2016-10-06 21:44:31 +0000
+++ python/Makefile 2016-10-20 21:22:41 +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-20 21:22:41 +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-20 21:22:41 +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
@@ -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-20 21:22:41 +0000
@@ -1,10 +1,15 @@
1# Copyright 2016 Canonical Limited. All rights reserved.1# Copyright 2016 Canonical Limited. All rights reserved.
22
3from contextlib import contextmanager
3import os4import os
5import shutil
6import tempfile
4import unittest7import unittest
58
6from txjuju import _juju1, _juju29from txjuju import _juju1, _juju2
7from txjuju._utils import Executable10from txjuju._utils import Executable
11import txjuju.cli
12import yaml
813
9from fakejuju.failures import Failures14from fakejuju.failures import Failures
10from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju15from fakejuju.fakejuju import get_filename, set_envvars, FakeJuju
@@ -44,9 +49,10 @@
44 def test_all_args(self):49 def test_all_args(self):
45 """set_envvars() works correctly when given all args."""50 """set_envvars() works correctly when given all args."""
46 envvars = {}51 envvars = {}
47 set_envvars(envvars, "/spam/failures", "/eggs/logsdir")52 set_envvars(envvars, "/spam", "/spam/failures", "/eggs/logsdir")
4853
49 self.assertEqual(envvars, {54 self.assertEqual(envvars, {
55 "FAKE_JUJU_DATA_DIR": "/spam",
50 "FAKE_JUJU_FAILURES": "/spam/failures",56 "FAKE_JUJU_FAILURES": "/spam/failures",
51 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",57 "FAKE_JUJU_LOGS_DIR": "/eggs/logsdir",
52 })58 })
@@ -57,6 +63,7 @@
57 set_envvars(envvars)63 set_envvars(envvars)
5864
59 self.assertEqual(envvars, {65 self.assertEqual(envvars, {
66 "FAKE_JUJU_DATA_DIR": "",
60 "FAKE_JUJU_FAILURES": "",67 "FAKE_JUJU_FAILURES": "",
61 "FAKE_JUJU_LOGS_DIR": "",68 "FAKE_JUJU_LOGS_DIR": "",
62 })69 })
@@ -64,9 +71,10 @@
64 def test_start_empty(self):71 def test_start_empty(self):
65 """set_envvars() sets all values on an empty dict."""72 """set_envvars() sets all values on an empty dict."""
66 envvars = {}73 envvars = {}
67 set_envvars(envvars, "x", "y")74 set_envvars(envvars, "w", "x", "y")
6875
69 self.assertEqual(envvars, {76 self.assertEqual(envvars, {
77 "FAKE_JUJU_DATA_DIR": "w",
70 "FAKE_JUJU_FAILURES": "x",78 "FAKE_JUJU_FAILURES": "x",
71 "FAKE_JUJU_LOGS_DIR": "y",79 "FAKE_JUJU_LOGS_DIR": "y",
72 })80 })
@@ -74,10 +82,11 @@
74 def test_no_collisions(self):82 def test_no_collisions(self):
75 """set_envvars() sets all values when none are set yet."""83 """set_envvars() sets all values when none are set yet."""
76 envvars = {"SPAM": "eggs"}84 envvars = {"SPAM": "eggs"}
77 set_envvars(envvars, "x", "y")85 set_envvars(envvars, "w", "x", "y")
7886
79 self.assertEqual(envvars, {87 self.assertEqual(envvars, {
80 "SPAM": "eggs",88 "SPAM": "eggs",
89 "FAKE_JUJU_DATA_DIR": "w",
81 "FAKE_JUJU_FAILURES": "x",90 "FAKE_JUJU_FAILURES": "x",
82 "FAKE_JUJU_LOGS_DIR": "y",91 "FAKE_JUJU_LOGS_DIR": "y",
83 })92 })
@@ -85,12 +94,14 @@
85 def test_empty_to_nonempty(self):94 def test_empty_to_nonempty(self):
86 """set_envvars() updates empty values."""95 """set_envvars() updates empty values."""
87 envvars = {96 envvars = {
97 "FAKE_JUJU_DATA_DIR": "",
88 "FAKE_JUJU_FAILURES": "",98 "FAKE_JUJU_FAILURES": "",
89 "FAKE_JUJU_LOGS_DIR": "",99 "FAKE_JUJU_LOGS_DIR": "",
90 }100 }
91 set_envvars(envvars, "x", "y")101 set_envvars(envvars, "w", "x", "y")
92102
93 self.assertEqual(envvars, {103 self.assertEqual(envvars, {
104 "FAKE_JUJU_DATA_DIR": "w",
94 "FAKE_JUJU_FAILURES": "x",105 "FAKE_JUJU_FAILURES": "x",
95 "FAKE_JUJU_LOGS_DIR": "y",106 "FAKE_JUJU_LOGS_DIR": "y",
96 })107 })
@@ -98,12 +109,14 @@
98 def test_nonempty_to_nonempty(self):109 def test_nonempty_to_nonempty(self):
99 """set_envvars() overwrites existing values."""110 """set_envvars() overwrites existing values."""
100 envvars = {111 envvars = {
112 "FAKE_JUJU_DATA_DIR": "spam",
101 "FAKE_JUJU_FAILURES": "spam",113 "FAKE_JUJU_FAILURES": "spam",
102 "FAKE_JUJU_LOGS_DIR": "ham",114 "FAKE_JUJU_LOGS_DIR": "ham",
103 }115 }
104 set_envvars(envvars, "x", "y")116 set_envvars(envvars, "w", "x", "y")
105117
106 self.assertEqual(envvars, {118 self.assertEqual(envvars, {
119 "FAKE_JUJU_DATA_DIR": "w",
107 "FAKE_JUJU_FAILURES": "x",120 "FAKE_JUJU_FAILURES": "x",
108 "FAKE_JUJU_LOGS_DIR": "y",121 "FAKE_JUJU_LOGS_DIR": "y",
109 })122 })
@@ -111,12 +124,14 @@
111 def test_nonempty_to_empty(self):124 def test_nonempty_to_empty(self):
112 """set_envvars() with no args "unsets" existing values."""125 """set_envvars() with no args "unsets" existing values."""
113 envvars = {126 envvars = {
127 "FAKE_JUJU_DATA_DIR": "w",
114 "FAKE_JUJU_FAILURES": "x",128 "FAKE_JUJU_FAILURES": "x",
115 "FAKE_JUJU_LOGS_DIR": "y",129 "FAKE_JUJU_LOGS_DIR": "y",
116 }130 }
117 set_envvars(envvars)131 set_envvars(envvars)
118132
119 self.assertEqual(envvars, {133 self.assertEqual(envvars, {
134 "FAKE_JUJU_DATA_DIR": "",
120 "FAKE_JUJU_FAILURES": "",135 "FAKE_JUJU_FAILURES": "",
121 "FAKE_JUJU_LOGS_DIR": "",136 "FAKE_JUJU_LOGS_DIR": "",
122 })137 })
@@ -131,7 +146,7 @@
131146
132 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")147 self.assertEqual(juju.filename, "/bin/dir/fake-juju-1.25.6")
133 self.assertEqual(juju.version, "1.25.6")148 self.assertEqual(juju.version, "1.25.6")
134 self.assertEqual(juju.cfgdir, "/a/juju/home")149 self.assertEqual(juju.datadir, "/a/juju/home")
135 self.assertEqual(juju.logsdir, "/logs/dir")150 self.assertEqual(juju.logsdir, "/logs/dir")
136 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")151 self.assertEqual(juju.failures.filename, "/failures/dir/juju-failures")
137152
@@ -141,19 +156,20 @@
141156
142 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")157 self.assertEqual(juju.filename, "/usr/bin/fake-juju-1.25.6")
143 self.assertEqual(juju.version, "1.25.6")158 self.assertEqual(juju.version, "1.25.6")
144 self.assertEqual(juju.cfgdir, "/my/juju/home")159 self.assertEqual(juju.datadir, "/my/juju/home")
145 self.assertEqual(juju.logsdir, "/my/juju/home")160 self.assertEqual(juju.logsdir, "/my/juju/home")
146 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")161 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
147162
148 def test_full(self):163 def test_full(self):
149 """FakeJuju() works correctly when given all args."""164 """FakeJuju() works correctly when given all args."""
150 cfgdir = "/my/juju/home"165 datadir = "/my/juju/home"
151 failures = Failures(cfgdir)166 failures = Failures(datadir)
152 juju = FakeJuju("/fake-juju", "1.25.6", cfgdir, "/some/logs", failures)167 juju = FakeJuju(
168 "/fake-juju", "1.25.6", datadir, "/some/logs", failures)
153169
154 self.assertEqual(juju.filename, "/fake-juju")170 self.assertEqual(juju.filename, "/fake-juju")
155 self.assertEqual(juju.version, "1.25.6")171 self.assertEqual(juju.version, "1.25.6")
156 self.assertEqual(juju.cfgdir, cfgdir)172 self.assertEqual(juju.datadir, datadir)
157 self.assertEqual(juju.logsdir, "/some/logs")173 self.assertEqual(juju.logsdir, "/some/logs")
158 self.assertIs(juju.failures, failures)174 self.assertIs(juju.failures, failures)
159175
@@ -163,7 +179,7 @@
163179
164 self.assertEqual(juju.filename, "/fake-juju")180 self.assertEqual(juju.filename, "/fake-juju")
165 self.assertEqual(juju.version, "1.25.6")181 self.assertEqual(juju.version, "1.25.6")
166 self.assertEqual(juju.cfgdir, "/my/juju/home")182 self.assertEqual(juju.datadir, "/my/juju/home")
167 self.assertEqual(juju.logsdir, "/my/juju/home")183 self.assertEqual(juju.logsdir, "/my/juju/home")
168 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")184 self.assertEqual(juju.failures.filename, "/my/juju/home/juju-failures")
169185
@@ -174,7 +190,7 @@
174 juju_unicode = FakeJuju(190 juju_unicode = FakeJuju(
175 u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/..."))191 u"/fake-juju", u"1.25.6", u"/x", u"/y", Failures(u"/..."))
176192
177 for name in ('filename version cfgdir logsdir'.split()):193 for name in ('filename version datadir logsdir'.split()):
178 self.assertIsInstance(getattr(juju_str, name), str)194 self.assertIsInstance(getattr(juju_str, name), str)
179 self.assertIsInstance(getattr(juju_unicode, name), unicode)195 self.assertIsInstance(getattr(juju_unicode, name), unicode)
180196
@@ -192,8 +208,8 @@
192 with self.assertRaises(ValueError):208 with self.assertRaises(ValueError):
193 FakeJuju("/fake-juju", "", "/my/juju/home")209 FakeJuju("/fake-juju", "", "/my/juju/home")
194210
195 def test_missing_cfgdir(self):211 def test_missing_datadir(self):
196 """FakeJuju() fails if cfgdir is None or empty."""212 """FakeJuju() fails if datadir is None or empty."""
197 with self.assertRaises(ValueError):213 with self.assertRaises(ValueError):
198 FakeJuju("/fake-juju", "1.25.6", None)214 FakeJuju("/fake-juju", "1.25.6", None)
199 with self.assertRaises(ValueError):215 with self.assertRaises(ValueError):
@@ -226,44 +242,131 @@
226 def test_cli_full(self):242 def test_cli_full(self):
227 """FakeJuju.cli() works correctly when given all args."""243 """FakeJuju.cli() works correctly when given all args."""
228 juju = FakeJuju("/fake-juju", "1.25.6", "/x")244 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
229 cli = juju.cli({"SPAM": "eggs"})245 cli = juju.cli("/y", {"SPAM": "eggs"})
230246
231 self.assertEqual(247 self.assertEqual(
232 cli._exe,248 cli._exe,
233 Executable("/fake-juju", {249 Executable("/fake-juju", {
234 "SPAM": "eggs",250 "SPAM": "eggs",
251 "FAKE_JUJU_DATA_DIR": "/x",
235 "FAKE_JUJU_FAILURES": "/x/juju-failures",252 "FAKE_JUJU_FAILURES": "/x/juju-failures",
236 "FAKE_JUJU_LOGS_DIR": "/x",253 "FAKE_JUJU_LOGS_DIR": "/x",
237 "JUJU_HOME": "/x",254 "JUJU_HOME": "/y",
238 }),255 }),
239 )256 )
240257
241 def test_cli_minimal(self):258 def test_cli_minimal(self):
242 """FakeJuju.cli() works correctly when given minimal args."""259 """FakeJuju.cli() works correctly when given minimal args."""
243 juju = FakeJuju("/fake-juju", "1.25.6", "/x")260 juju = FakeJuju("/fake-juju", "1.25.6", "/x")
244 cli = juju.cli()261 cli = juju.cli("/y")
245262
246 self.assertEqual(263 self.assertEqual(
247 cli._exe,264 cli._exe,
248 Executable("/fake-juju", dict(os.environ, **{265 Executable("/fake-juju", dict(os.environ, **{
266 "FAKE_JUJU_DATA_DIR": "/x",
249 "FAKE_JUJU_FAILURES": "/x/juju-failures",267 "FAKE_JUJU_FAILURES": "/x/juju-failures",
250 "FAKE_JUJU_LOGS_DIR": "/x",268 "FAKE_JUJU_LOGS_DIR": "/x",
251 "JUJU_HOME": "/x",269 "JUJU_HOME": "/y",
252 })),270 })),
253 )271 )
254272
255 def test_cli_juju1(self):273 def test_cli_juju1(self):
256 """FakeJuju.cli() works correctly for Juju 1.x."""274 """FakeJuju.cli() works correctly for Juju 1.x."""
257 juju = FakeJuju.from_version("1.25.6", "/x")275 juju = FakeJuju.from_version("1.25.6", "/x")
258 cli = juju.cli()276 cli = juju.cli("/y")
259277
260 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/x")278 self.assertEqual(cli._exe.envvars["JUJU_HOME"], "/y")
261 self.assertIsInstance(cli._juju, _juju1.CLIHooks)279 self.assertIsInstance(cli._juju, _juju1.CLIHooks)
262280
263 def test_cli_juju2(self):281 def test_cli_juju2(self):
264 """FakeJuju.cli() works correctly for Juju 2.x."""282 """FakeJuju.cli() works correctly for Juju 2.x."""
265 juju = FakeJuju.from_version("2.0.0", "/x")283 juju = FakeJuju.from_version("2.0.0", "/x")
266 cli = juju.cli()284 cli = juju.cli("/y")
267285
268 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/x")286 self.assertEqual(cli._exe.envvars["JUJU_DATA"], "/y")
269 self.assertIsInstance(cli._juju, _juju2.CLIHooks)287 self.assertIsInstance(cli._juju, _juju2.CLIHooks)
288
289 def test_bootstrap(self):
290 """FakeJuju.bootstrap() bootstraps from scratch using fake-juju."""
291 with tempdir() as datadir:
292 fakejuju = FakeJuju.from_version("1.25.6", datadir)
293 cfgdir = os.path.join(datadir, "juju")
294 cli, api_info = fakejuju.bootstrap("spam", cfgdir, "secret")
295 port = api_info[None].address.split(":")[-1]
296
297 files = os.listdir(datadir)
298 files.extend(os.path.join("juju", name)
299 for name in os.listdir(cfgdir))
300 files.sort()
301 with open(os.path.join(cfgdir, "environments.yaml")) as envfile:
302 data = envfile.read()
303
304 cli.destroy_controller()
305
306 self.maxDiff = None
307 self.assertEqual(api_info, {
308 'controller': txjuju.cli.APIInfo(
309 endpoints=['localhost:' + port],
310 user='admin',
311 password='secret',
312 model_uuid='deadbeef-0bad-400d-8000-4b1d0d06f00d',
313 ),
314 None: txjuju.cli.APIInfo(
315 endpoints=['localhost:' + port],
316 user='admin',
317 password='secret',
318 model_uuid=None,
319 ),
320 })
321 self.assertItemsEqual(files, [
322 'cert.ca',
323 'fake-juju.log',
324 'fakejuju',
325 'fifo',
326 'juju',
327 'juju/environments',
328 'juju/environments.yaml',
329 ])
330 self.assertEqual(yaml.load(data), {
331 "environments": {
332 "spam": {
333 "admin-secret": "secret",
334 "default-series": "trusty",
335 "type": "dummy",
336 },
337 },
338 })
339
340 def test_is_bootstrapped_true(self):
341 """FakeJuju.is_bootstrapped() returns True if the fifo file exists."""
342 with tempdir() as datadir:
343 fakejuju = FakeJuju.from_version("1.25.6", datadir)
344 with open(fakejuju.fifo, "w"):
345 pass
346 result = fakejuju.is_bootstrapped()
347
348 self.assertTrue(result)
349
350 def test_is_bootstrapped_false(self):
351 """FakeJuju.is_bootstrapped() returns False if the fifo is gone."""
352 with tempdir() as datadir:
353 fakejuju = FakeJuju.from_version("1.25.6", datadir)
354 result = fakejuju.is_bootstrapped()
355
356 self.assertFalse(result)
357
358 def test_is_bootstrapped_datadir_missing(self):
359 """FakeJuju.is_bootstrapped() returns False if the data dir is gone."""
360 fakejuju = FakeJuju.from_version("1.25.6", "/tmp/fakejuju-no-exist")
361 result = fakejuju.is_bootstrapped()
362
363 self.assertFalse(result)
364
365
366@contextmanager
367def tempdir():
368 cfgdir = tempfile.mkdtemp("fakejuju-test-")
369 try:
370 yield cfgdir
371 finally:
372 shutil.rmtree(cfgdir)

Subscribers

People subscribed via source and target branches

to all changes: