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