Merge lp:~axwalk/juju-core/lp1296475-block-sigint-bootstrap into lp:~go-bot/juju-core/trunk
- lp1296475-block-sigint-bootstrap
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Andrew Wilkins |
Approved revision: | no longer in the source branch. |
Merged at revision: | 2481 |
Proposed branch: | lp:~axwalk/juju-core/lp1296475-block-sigint-bootstrap |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
442 lines (+201/-23) 13 files modified
cmd/juju/bootstrap.go (+14/-1) cmd/juju/bootstrap_test.go (+8/-5) cmd/juju/common.go (+1/-0) environs/bootstrap/bootstrap_test.go (+6/-6) environs/bootstrap/export_test.go (+8/-0) environs/bootstrap/interruptiblestorage.go (+62/-0) environs/bootstrap/interruptiblestorage_test.go (+74/-0) environs/bootstrap/synctools.go (+20/-5) environs/interface.go (+2/-0) provider/common/bootstrap.go (+3/-3) provider/dummy/environs.go (+1/-1) provider/local/environ.go (+1/-1) provider/manual/environ.go (+1/-1) |
To merge this branch: | bzr merge lp:~axwalk/juju-core/lp1296475-block-sigint-bootstrap |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+212558@code.launchpad.net |
Commit message
Block SIGINT during bootstrap
This change is to catch SIGINT during bootstrap
for all providers. Also, if SIGINT is delivered
while tools are being uploaded, the upload will
be cancelled.
If the user Ctrl-C's while the local provider's
bootstrap script is being executed, it will be
interrupted and will return and error to juju.
Juju will then attempt to destroy the environment
and remove the .jenv file.
We will now also send some very basic feedback to
stderr when uploading tools.
Fixes lp:1296475
Description of the change
Block SIGINT during bootstrap
This change is to catch SIGINT during bootstrap
for all providers. Also, if SIGINT is delivered
while tools are being uploaded, the upload will
be cancelled.
If the user Ctrl-C's while the local provider's
bootstrap script is being executed, it will be
interrupted and will return and error to juju.
Juju will then attempt to destroy the environment
and remove the .jenv file.
We will now also send some very basic feedback to
stderr when uploading tools.
Fixes lp:1296475
Andrew Wilkins (axwalk) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
Tim Penhey (thumper) wrote : | # |
https:/
File cmd/juju/
https:/
cmd/juju/
signalled: waiting for bootstrap to exit")
You could use the new awesome:
ctx.
https:/
File cmd/juju/common.go (right):
https:/
cmd/juju/
destroying environment\n", action)
ctx.Infof("%s failed, destroying environment", action)
A new line is added to the end if missing.
https:/
File environs/
https:/
environs/
Is it expected that this will continue until finished even if
interrupted?
Is there a race condition here between the return 0, interruptedError
below and this assignment?
https:/
File environs/
https:/
environs/
is possible")
we could change this now to use the Verbosef method (if we add it to the
BootstrapContext interface)
https:/
environs/
"cancelling tools upload")
we should put Infof and Verbosef onto the bootstrap context I think.
Andrew Wilkins (axwalk) wrote : | # |
Please take a look.
https:/
File cmd/juju/
https:/
cmd/juju/
signalled: waiting for bootstrap to exit")
On 2014/03/26 02:41:47, thumper wrote:
> You could use the new awesome:
> ctx.Infof(
Done.
https:/
File cmd/juju/common.go (right):
https:/
cmd/juju/
destroying environment\n", action)
On 2014/03/26 02:41:47, thumper wrote:
> ctx.Infof("%s failed, destroying environment", action)
> A new line is added to the end if missing.
Done.
https:/
File environs/
https:/
environs/
On 2014/03/26 02:41:47, thumper wrote:
> Is it expected that this will continue until finished even if
interrupted?
> Is there a race condition here between the return 0, interruptedError
below and
> this assignment?
Yes, good call. Updated to use locals rather than named results.
https:/
File environs/
https:/
environs/
is possible")
On 2014/03/26 02:41:47, thumper wrote:
> we could change this now to use the Verbosef method (if we add it to
the
> BootstrapContext interface)
I don't think this particular one should be logged to stderr, but I have
added Infof/Verbosef to BootstrapContext and changed the other ones
over.
https:/
environs/
"cancelling tools upload")
On 2014/03/26 02:41:47, thumper wrote:
> we should put Infof and Verbosef onto the bootstrap context I think.
Done.
Tim Penhey (thumper) wrote : | # |
Wayne Witzel III (wwitzel3) wrote : | # |
Preview Diff
1 | === modified file 'cmd/juju/bootstrap.go' | |||
2 | --- cmd/juju/bootstrap.go 2014-03-18 05:03:38 +0000 | |||
3 | +++ cmd/juju/bootstrap.go 2014-03-26 03:32:09 +0000 | |||
4 | @@ -100,6 +100,19 @@ | |||
5 | 100 | if err := bootstrap.EnsureNotBootstrapped(environ); err != nil { | 100 | if err := bootstrap.EnsureNotBootstrapped(environ); err != nil { |
6 | 101 | return err | 101 | return err |
7 | 102 | } | 102 | } |
8 | 103 | |||
9 | 104 | // Block interruption during bootstrap. Providers may also | ||
10 | 105 | // register for interrupt notification so they can exit early. | ||
11 | 106 | interrupted := make(chan os.Signal, 1) | ||
12 | 107 | defer close(interrupted) | ||
13 | 108 | ctx.InterruptNotify(interrupted) | ||
14 | 109 | defer ctx.StopInterruptNotify(interrupted) | ||
15 | 110 | go func() { | ||
16 | 111 | for _ = range interrupted { | ||
17 | 112 | ctx.Infof("Interrupt signalled: waiting for bootstrap to exit") | ||
18 | 113 | } | ||
19 | 114 | }() | ||
20 | 115 | |||
21 | 103 | // If --metadata-source is specified, override the default tools metadata source so | 116 | // If --metadata-source is specified, override the default tools metadata source so |
22 | 104 | // SyncTools can use it, and also upload any image metadata. | 117 | // SyncTools can use it, and also upload any image metadata. |
23 | 105 | if c.MetadataSource != "" { | 118 | if c.MetadataSource != "" { |
24 | @@ -122,7 +135,7 @@ | |||
25 | 122 | c.UploadTools = true | 135 | c.UploadTools = true |
26 | 123 | } | 136 | } |
27 | 124 | if c.UploadTools { | 137 | if c.UploadTools { |
29 | 125 | err = bootstrap.UploadTools(environ, c.Constraints.Arch, true, c.Series...) | 138 | err = bootstrap.UploadTools(ctx, environ, c.Constraints.Arch, true, c.Series...) |
30 | 126 | if err != nil { | 139 | if err != nil { |
31 | 127 | return err | 140 | return err |
32 | 128 | } | 141 | } |
33 | 129 | 142 | ||
34 | === modified file 'cmd/juju/bootstrap_test.go' | |||
35 | --- cmd/juju/bootstrap_test.go 2014-03-24 20:08:45 +0000 | |||
36 | +++ cmd/juju/bootstrap_test.go 2014-03-26 03:32:09 +0000 | |||
37 | @@ -374,7 +374,9 @@ | |||
38 | 374 | ctx2 := coretesting.Context(c) | 374 | ctx2 := coretesting.Context(c) |
39 | 375 | code2 := cmd.Main(&BootstrapCommand{}, ctx2, nil) | 375 | code2 := cmd.Main(&BootstrapCommand{}, ctx2, nil) |
40 | 376 | c.Check(code2, gc.Equals, 1) | 376 | c.Check(code2, gc.Equals, 1) |
42 | 377 | c.Check(coretesting.Stderr(ctx2), gc.Equals, "error: environment is already bootstrapped\n") | 377 | expectedErrText := "Bootstrap failed, destroying environment\n" |
43 | 378 | expectedErrText += "error: environment is already bootstrapped\n" | ||
44 | 379 | c.Check(coretesting.Stderr(ctx2), gc.Equals, expectedErrText) | ||
45 | 378 | c.Check(coretesting.Stdout(ctx2), gc.Equals, "") | 380 | c.Check(coretesting.Stdout(ctx2), gc.Equals, "") |
46 | 379 | } | 381 | } |
47 | 380 | 382 | ||
48 | @@ -540,8 +542,8 @@ | |||
49 | 540 | code := cmd.Main(&BootstrapCommand{}, context, nil) | 542 | code := cmd.Main(&BootstrapCommand{}, context, nil) |
50 | 541 | c.Assert(code, gc.Equals, 1) | 543 | c.Assert(code, gc.Equals, 1) |
51 | 542 | errText := context.Stderr.(*bytes.Buffer).String() | 544 | errText := context.Stderr.(*bytes.Buffer).String() |
54 | 543 | errText = strings.Replace(errText, "\n", "", -1) | 545 | expectedErrText := "Bootstrap failed, destroying environment\n" |
55 | 544 | expectedErrText := "error: cannot upload bootstrap tools: Juju cannot bootstrap because no tools are available for your environment.*" | 546 | expectedErrText += "error: cannot upload bootstrap tools: Juju cannot bootstrap because no tools are available for your environment(.|\n)*" |
56 | 545 | c.Assert(errText, gc.Matches, expectedErrText) | 547 | c.Assert(errText, gc.Matches, expectedErrText) |
57 | 546 | } | 548 | } |
58 | 547 | 549 | ||
59 | @@ -556,8 +558,9 @@ | |||
60 | 556 | code := cmd.Main(&BootstrapCommand{}, context, nil) | 558 | code := cmd.Main(&BootstrapCommand{}, context, nil) |
61 | 557 | c.Assert(code, gc.Equals, 1) | 559 | c.Assert(code, gc.Equals, 1) |
62 | 558 | errText := context.Stderr.(*bytes.Buffer).String() | 560 | errText := context.Stderr.(*bytes.Buffer).String() |
65 | 559 | errText = strings.Replace(errText, "\n", "", -1) | 561 | expectedErrText := "uploading tools for series \\[precise raring\\]\n" |
66 | 560 | expectedErrText := "error: cannot upload bootstrap tools: an error" | 562 | expectedErrText += "Bootstrap failed, destroying environment\n" |
67 | 563 | expectedErrText += "error: cannot upload bootstrap tools: an error\n" | ||
68 | 561 | c.Assert(errText, gc.Matches, expectedErrText) | 564 | c.Assert(errText, gc.Matches, expectedErrText) |
69 | 562 | } | 565 | } |
70 | 563 | 566 | ||
71 | 564 | 567 | ||
72 | === modified file 'cmd/juju/common.go' | |||
73 | --- cmd/juju/common.go 2014-03-10 00:45:41 +0000 | |||
74 | +++ cmd/juju/common.go 2014-03-26 03:32:09 +0000 | |||
75 | @@ -39,6 +39,7 @@ | |||
76 | 39 | } | 39 | } |
77 | 40 | cleanup := func() { | 40 | cleanup := func() { |
78 | 41 | if !existing { | 41 | if !existing { |
79 | 42 | ctx.Infof("%s failed, destroying environment", action) | ||
80 | 42 | destroyPreparedEnviron(environ, store, resultErr, action) | 43 | destroyPreparedEnviron(environ, store, resultErr, action) |
81 | 43 | } | 44 | } |
82 | 44 | } | 45 | } |
83 | 45 | 46 | ||
84 | === modified file 'environs/bootstrap/bootstrap_test.go' | |||
85 | --- environs/bootstrap/bootstrap_test.go 2014-03-19 22:02:00 +0000 | |||
86 | +++ environs/bootstrap/bootstrap_test.go 2014-03-26 03:32:09 +0000 | |||
87 | @@ -216,7 +216,7 @@ | |||
88 | 216 | s.setDummyStorage(c, env) | 216 | s.setDummyStorage(c, env) |
89 | 217 | envtesting.RemoveFakeTools(c, env.Storage()) | 217 | envtesting.RemoveFakeTools(c, env.Storage()) |
90 | 218 | arch := "ppc64" | 218 | arch := "ppc64" |
92 | 219 | _, err := bootstrap.EnsureToolsAvailability(env, env.Config().DefaultSeries(), &arch) | 219 | _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), &arch) |
93 | 220 | c.Assert(err, gc.NotNil) | 220 | c.Assert(err, gc.NotNil) |
94 | 221 | stripped := strings.Replace(err.Error(), "\n", "", -1) | 221 | stripped := strings.Replace(err.Error(), "\n", "", -1) |
95 | 222 | c.Assert(stripped, | 222 | c.Assert(stripped, |
96 | @@ -232,7 +232,7 @@ | |||
97 | 232 | env := newEnviron("foo", useDefaultKeys, nil) | 232 | env := newEnviron("foo", useDefaultKeys, nil) |
98 | 233 | s.setDummyStorage(c, env) | 233 | s.setDummyStorage(c, env) |
99 | 234 | envtesting.RemoveFakeTools(c, env.Storage()) | 234 | envtesting.RemoveFakeTools(c, env.Storage()) |
101 | 235 | _, err := bootstrap.EnsureToolsAvailability(env, env.Config().DefaultSeries(), nil) | 235 | _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil) |
102 | 236 | c.Assert(err, gc.NotNil) | 236 | c.Assert(err, gc.NotNil) |
103 | 237 | stripped := strings.Replace(err.Error(), "\n", "", -1) | 237 | stripped := strings.Replace(err.Error(), "\n", "", -1) |
104 | 238 | c.Assert(stripped, | 238 | c.Assert(stripped, |
105 | @@ -245,7 +245,7 @@ | |||
106 | 245 | env := newEnviron("foo", useDefaultKeys, map[string]interface{}{"agent-version": "1.16.0"}) | 245 | env := newEnviron("foo", useDefaultKeys, map[string]interface{}{"agent-version": "1.16.0"}) |
107 | 246 | s.setDummyStorage(c, env) | 246 | s.setDummyStorage(c, env) |
108 | 247 | envtesting.RemoveFakeTools(c, env.Storage()) | 247 | envtesting.RemoveFakeTools(c, env.Storage()) |
110 | 248 | _, err := bootstrap.EnsureToolsAvailability(env, env.Config().DefaultSeries(), nil) | 248 | _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil) |
111 | 249 | c.Assert(err, gc.NotNil) | 249 | c.Assert(err, gc.NotNil) |
112 | 250 | stripped := strings.Replace(err.Error(), "\n", "", -1) | 250 | stripped := strings.Replace(err.Error(), "\n", "", -1) |
113 | 251 | c.Assert(stripped, | 251 | c.Assert(stripped, |
114 | @@ -259,7 +259,7 @@ | |||
115 | 259 | env := newEnviron("foo", useDefaultKeys, nil) | 259 | env := newEnviron("foo", useDefaultKeys, nil) |
116 | 260 | s.setDummyStorage(c, env) | 260 | s.setDummyStorage(c, env) |
117 | 261 | envtesting.RemoveFakeTools(c, env.Storage()) | 261 | envtesting.RemoveFakeTools(c, env.Storage()) |
119 | 262 | _, err := bootstrap.EnsureToolsAvailability(env, env.Config().DefaultSeries(), nil) | 262 | _, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), nil) |
120 | 263 | c.Assert(err, gc.NotNil) | 263 | c.Assert(err, gc.NotNil) |
121 | 264 | stripped := strings.Replace(err.Error(), "\n", "", -1) | 264 | stripped := strings.Replace(err.Error(), "\n", "", -1) |
122 | 265 | c.Assert(stripped, | 265 | c.Assert(stripped, |
123 | @@ -309,7 +309,7 @@ | |||
124 | 309 | return "arm64" | 309 | return "arm64" |
125 | 310 | }) | 310 | }) |
126 | 311 | arch := "arm64" | 311 | arch := "arm64" |
128 | 312 | agentTools, err := bootstrap.EnsureToolsAvailability(env, env.Config().DefaultSeries(), &arch) | 312 | agentTools, err := bootstrap.EnsureToolsAvailability(coretesting.Context(c), env, env.Config().DefaultSeries(), &arch) |
129 | 313 | c.Assert(err, gc.IsNil) | 313 | c.Assert(err, gc.IsNil) |
130 | 314 | c.Assert(agentTools, gc.HasLen, 1) | 314 | c.Assert(agentTools, gc.HasLen, 1) |
131 | 315 | expectedVers := version.Current | 315 | expectedVers := version.Current |
132 | @@ -352,7 +352,7 @@ | |||
133 | 352 | return "arm64" | 352 | return "arm64" |
134 | 353 | }) | 353 | }) |
135 | 354 | arch := "arm64" | 354 | arch := "arm64" |
137 | 355 | err := bootstrap.UploadTools(env, &arch, allowRelease, "precise") | 355 | err := bootstrap.UploadTools(coretesting.Context(c), env, &arch, allowRelease, "precise") |
138 | 356 | if errMessage != "" { | 356 | if errMessage != "" { |
139 | 357 | stripped := strings.Replace(err.Error(), "\n", "", -1) | 357 | stripped := strings.Replace(err.Error(), "\n", "", -1) |
140 | 358 | c.Assert(stripped, gc.Matches, errMessage) | 358 | c.Assert(stripped, gc.Matches, errMessage) |
141 | 359 | 359 | ||
142 | === added file 'environs/bootstrap/export_test.go' | |||
143 | --- environs/bootstrap/export_test.go 1970-01-01 00:00:00 +0000 | |||
144 | +++ environs/bootstrap/export_test.go 2014-03-26 03:32:09 +0000 | |||
145 | @@ -0,0 +1,8 @@ | |||
146 | 1 | // Copyright 2014 Canonical Ltd. | ||
147 | 2 | // Licensed under the AGPLv3, see LICENCE file for details. | ||
148 | 3 | |||
149 | 4 | package bootstrap | ||
150 | 5 | |||
151 | 6 | var ( | ||
152 | 7 | NewInterruptibleStorage = newInterruptibleStorage | ||
153 | 8 | ) | ||
154 | 0 | 9 | ||
155 | === added file 'environs/bootstrap/interruptiblestorage.go' | |||
156 | --- environs/bootstrap/interruptiblestorage.go 1970-01-01 00:00:00 +0000 | |||
157 | +++ environs/bootstrap/interruptiblestorage.go 2014-03-26 03:32:09 +0000 | |||
158 | @@ -0,0 +1,62 @@ | |||
159 | 1 | // Copyright 2014 Canonical Ltd. | ||
160 | 2 | // Licensed under the AGPLv3, see LICENCE file for details. | ||
161 | 3 | |||
162 | 4 | package bootstrap | ||
163 | 5 | |||
164 | 6 | import ( | ||
165 | 7 | "errors" | ||
166 | 8 | "io" | ||
167 | 9 | |||
168 | 10 | "launchpad.net/juju-core/environs/storage" | ||
169 | 11 | ) | ||
170 | 12 | |||
171 | 13 | var interruptedError = errors.New("interrupted") | ||
172 | 14 | |||
173 | 15 | // interruptibleStorage is a storage.Storage that sits | ||
174 | 16 | // between the user and another Storage, allowing the | ||
175 | 17 | // Put method to be interrupted. | ||
176 | 18 | type interruptibleStorage struct { | ||
177 | 19 | storage.Storage | ||
178 | 20 | interrupt <-chan struct{} | ||
179 | 21 | } | ||
180 | 22 | |||
181 | 23 | // newInterruptibleStorage wraps the provided Storage so that Put | ||
182 | 24 | // will immediately return an error if the provided channel is | ||
183 | 25 | // closed. | ||
184 | 26 | func newInterruptibleStorage(s storage.Storage, interrupt <-chan struct{}) storage.Storage { | ||
185 | 27 | return &interruptibleStorage{s, interrupt} | ||
186 | 28 | } | ||
187 | 29 | |||
188 | 30 | type interruptibleReader struct { | ||
189 | 31 | io.Reader | ||
190 | 32 | interrupt <-chan struct{} | ||
191 | 33 | } | ||
192 | 34 | |||
193 | 35 | func (r *interruptibleReader) Read(p []byte) (int, error) { | ||
194 | 36 | // if the interrupt channel is already | ||
195 | 37 | // closed, just drop out immediately. | ||
196 | 38 | select { | ||
197 | 39 | case <-r.interrupt: | ||
198 | 40 | return 0, interruptedError | ||
199 | 41 | default: | ||
200 | 42 | } | ||
201 | 43 | |||
202 | 44 | // read and wait for interruption concurrently | ||
203 | 45 | var n int | ||
204 | 46 | var err error | ||
205 | 47 | done := make(chan struct{}) | ||
206 | 48 | go func() { | ||
207 | 49 | defer close(done) | ||
208 | 50 | n, err = r.Reader.Read(p) | ||
209 | 51 | }() | ||
210 | 52 | select { | ||
211 | 53 | case <-done: | ||
212 | 54 | return n, err | ||
213 | 55 | case <-r.interrupt: | ||
214 | 56 | return 0, interruptedError | ||
215 | 57 | } | ||
216 | 58 | } | ||
217 | 59 | |||
218 | 60 | func (s *interruptibleStorage) Put(name string, r io.Reader, length int64) error { | ||
219 | 61 | return s.Storage.Put(name, &interruptibleReader{r, s.interrupt}, length) | ||
220 | 62 | } | ||
221 | 0 | 63 | ||
222 | === added file 'environs/bootstrap/interruptiblestorage_test.go' | |||
223 | --- environs/bootstrap/interruptiblestorage_test.go 1970-01-01 00:00:00 +0000 | |||
224 | +++ environs/bootstrap/interruptiblestorage_test.go 2014-03-26 03:32:09 +0000 | |||
225 | @@ -0,0 +1,74 @@ | |||
226 | 1 | // Copyright 2014 Canonical Ltd. | ||
227 | 2 | // Licensed under the AGPLv3, see LICENCE file for details. | ||
228 | 3 | |||
229 | 4 | package bootstrap_test | ||
230 | 5 | |||
231 | 6 | import ( | ||
232 | 7 | "fmt" | ||
233 | 8 | |||
234 | 9 | gc "launchpad.net/gocheck" | ||
235 | 10 | |||
236 | 11 | "launchpad.net/juju-core/environs/bootstrap" | ||
237 | 12 | envtesting "launchpad.net/juju-core/environs/testing" | ||
238 | 13 | "launchpad.net/juju-core/testing/testbase" | ||
239 | 14 | ) | ||
240 | 15 | |||
241 | 16 | type interruptibleStorageSuite struct { | ||
242 | 17 | testbase.LoggingSuite | ||
243 | 18 | } | ||
244 | 19 | |||
245 | 20 | var _ = gc.Suite(&interruptibleStorageSuite{}) | ||
246 | 21 | |||
247 | 22 | type errorReader struct { | ||
248 | 23 | close chan struct{} | ||
249 | 24 | wait chan struct{} | ||
250 | 25 | called int | ||
251 | 26 | err error | ||
252 | 27 | } | ||
253 | 28 | |||
254 | 29 | func (r *errorReader) Read(buf []byte) (int, error) { | ||
255 | 30 | if r.close != nil { | ||
256 | 31 | close(r.close) | ||
257 | 32 | } | ||
258 | 33 | if r.wait != nil { | ||
259 | 34 | <-r.wait | ||
260 | 35 | } | ||
261 | 36 | r.called++ | ||
262 | 37 | return 0, r.err | ||
263 | 38 | } | ||
264 | 39 | |||
265 | 40 | func (s *interruptibleStorageSuite) TestInterruptStorage(c *gc.C) { | ||
266 | 41 | closer, stor, _ := envtesting.CreateLocalTestStorage(c) | ||
267 | 42 | s.AddCleanup(func(c *gc.C) { closer.Close() }) | ||
268 | 43 | reader := &errorReader{ | ||
269 | 44 | err: fmt.Errorf("read failed"), | ||
270 | 45 | } | ||
271 | 46 | interrupted := make(chan struct{}) | ||
272 | 47 | istor := bootstrap.NewInterruptibleStorage(stor, interrupted) | ||
273 | 48 | |||
274 | 49 | err := istor.Put("name", reader, 3) | ||
275 | 50 | c.Assert(err, gc.ErrorMatches, ".*: read failed") | ||
276 | 51 | c.Assert(reader.called, gc.Equals, 1) | ||
277 | 52 | |||
278 | 53 | // If the channel is already closed, then the | ||
279 | 54 | // underlying reader is never deferred to. | ||
280 | 55 | close(interrupted) | ||
281 | 56 | err = istor.Put("name", reader, 3) | ||
282 | 57 | c.Assert(err, gc.ErrorMatches, ".*: interrupted") | ||
283 | 58 | c.Assert(reader.called, gc.Equals, 1) | ||
284 | 59 | } | ||
285 | 60 | |||
286 | 61 | func (s *interruptibleStorageSuite) TestInterruptStorageConcurrently(c *gc.C) { | ||
287 | 62 | closer, stor, _ := envtesting.CreateLocalTestStorage(c) | ||
288 | 63 | s.AddCleanup(func(c *gc.C) { closer.Close() }) | ||
289 | 64 | reader := &errorReader{ | ||
290 | 65 | close: make(chan struct{}), | ||
291 | 66 | wait: make(chan struct{}), | ||
292 | 67 | err: fmt.Errorf("read failed"), | ||
293 | 68 | } | ||
294 | 69 | istor := bootstrap.NewInterruptibleStorage(stor, reader.close) | ||
295 | 70 | err := istor.Put("name", reader, 3) | ||
296 | 71 | c.Assert(err, gc.ErrorMatches, ".*: interrupted") | ||
297 | 72 | c.Assert(reader.called, gc.Equals, 0) // reader is blocked | ||
298 | 73 | close(reader.wait) | ||
299 | 74 | } | ||
300 | 0 | 75 | ||
301 | === modified file 'environs/bootstrap/synctools.go' | |||
302 | --- environs/bootstrap/synctools.go 2014-03-19 22:02:00 +0000 | |||
303 | +++ environs/bootstrap/synctools.go 2014-03-26 03:32:09 +0000 | |||
304 | @@ -5,6 +5,7 @@ | |||
305 | 5 | 5 | ||
306 | 6 | import ( | 6 | import ( |
307 | 7 | "fmt" | 7 | "fmt" |
308 | 8 | "os" | ||
309 | 8 | 9 | ||
310 | 9 | "launchpad.net/juju-core/environs" | 10 | "launchpad.net/juju-core/environs" |
311 | 10 | "launchpad.net/juju-core/environs/config" | 11 | "launchpad.net/juju-core/environs/config" |
312 | @@ -30,7 +31,7 @@ | |||
313 | 30 | // the environment storage, after which it sets the agent-version. If forceVersion is true, | 31 | // the environment storage, after which it sets the agent-version. If forceVersion is true, |
314 | 31 | // we allow uploading release tools versions and allow uploading even when the agent-version is | 32 | // we allow uploading release tools versions and allow uploading even when the agent-version is |
315 | 32 | // already set in the environment. | 33 | // already set in the environment. |
317 | 33 | func UploadTools(env environs.Environ, toolsArch *string, forceVersion bool, bootstrapSeries ...string) error { | 34 | func UploadTools(ctx environs.BootstrapContext, env environs.Environ, toolsArch *string, forceVersion bool, bootstrapSeries ...string) error { |
318 | 34 | logger.Infof("checking that upload is possible") | 35 | logger.Infof("checking that upload is possible") |
319 | 35 | // Check the series are valid. | 36 | // Check the series are valid. |
320 | 36 | for _, series := range bootstrapSeries { | 37 | for _, series := range bootstrapSeries { |
321 | @@ -43,11 +44,25 @@ | |||
322 | 43 | return err | 44 | return err |
323 | 44 | } | 45 | } |
324 | 45 | 46 | ||
325 | 47 | // Make storage interruptible. | ||
326 | 48 | interrupted := make(chan os.Signal, 1) | ||
327 | 49 | interruptStorage := make(chan struct{}) | ||
328 | 50 | ctx.InterruptNotify(interrupted) | ||
329 | 51 | defer ctx.StopInterruptNotify(interrupted) | ||
330 | 52 | defer close(interrupted) | ||
331 | 53 | go func() { | ||
332 | 54 | defer close(interruptStorage) // closing interrupts all uploads | ||
333 | 55 | if _, ok := <-interrupted; ok { | ||
334 | 56 | ctx.Infof("cancelling tools upload") | ||
335 | 57 | } | ||
336 | 58 | }() | ||
337 | 59 | stor := newInterruptibleStorage(env.Storage(), interruptStorage) | ||
338 | 60 | |||
339 | 46 | cfg := env.Config() | 61 | cfg := env.Config() |
340 | 47 | explicitVersion := uploadVersion(version.Current.Number, nil) | 62 | explicitVersion := uploadVersion(version.Current.Number, nil) |
341 | 48 | uploadSeries := SeriesToUpload(cfg, bootstrapSeries) | 63 | uploadSeries := SeriesToUpload(cfg, bootstrapSeries) |
344 | 49 | logger.Infof("uploading tools for series %s", uploadSeries) | 64 | ctx.Infof("uploading tools for series %s", uploadSeries) |
345 | 50 | tools, err := sync.Upload(env.Storage(), &explicitVersion, uploadSeries...) | 65 | tools, err := sync.Upload(stor, &explicitVersion, uploadSeries...) |
346 | 51 | if err != nil { | 66 | if err != nil { |
347 | 52 | return err | 67 | return err |
348 | 53 | } | 68 | } |
349 | @@ -131,7 +146,7 @@ | |||
350 | 131 | 146 | ||
351 | 132 | // EnsureToolsAvailability verifies the tools are available. If no tools are | 147 | // EnsureToolsAvailability verifies the tools are available. If no tools are |
352 | 133 | // found, it will automatically synchronize them. | 148 | // found, it will automatically synchronize them. |
354 | 134 | func EnsureToolsAvailability(env environs.Environ, series string, toolsArch *string) (coretools.List, error) { | 149 | func EnsureToolsAvailability(ctx environs.BootstrapContext, env environs.Environ, series string, toolsArch *string) (coretools.List, error) { |
355 | 135 | cfg := env.Config() | 150 | cfg := env.Config() |
356 | 136 | var vers *version.Number | 151 | var vers *version.Number |
357 | 137 | if agentVersion, ok := cfg.AgentVersion(); ok { | 152 | if agentVersion, ok := cfg.AgentVersion(); ok { |
358 | @@ -160,7 +175,7 @@ | |||
359 | 160 | // No tools available so our only hope is to build locally and upload. | 175 | // No tools available so our only hope is to build locally and upload. |
360 | 161 | logger.Warningf("no prepackaged tools available") | 176 | logger.Warningf("no prepackaged tools available") |
361 | 162 | uploadSeries := SeriesToUpload(cfg, nil) | 177 | uploadSeries := SeriesToUpload(cfg, nil) |
363 | 163 | if err := UploadTools(env, toolsArch, false, append(uploadSeries, series)...); err != nil { | 178 | if err := UploadTools(ctx, env, toolsArch, false, append(uploadSeries, series)...); err != nil { |
364 | 164 | logger.Errorf("%s", noToolsMessage) | 179 | logger.Errorf("%s", noToolsMessage) |
365 | 165 | return nil, fmt.Errorf("cannot upload bootstrap tools: %v", err) | 180 | return nil, fmt.Errorf("cannot upload bootstrap tools: %v", err) |
366 | 166 | } | 181 | } |
367 | 167 | 182 | ||
368 | === modified file 'environs/interface.go' | |||
369 | --- environs/interface.go 2014-03-17 07:44:21 +0000 | |||
370 | +++ environs/interface.go 2014-03-26 03:32:09 +0000 | |||
371 | @@ -183,6 +183,8 @@ | |||
372 | 183 | GetStdin() io.Reader | 183 | GetStdin() io.Reader |
373 | 184 | GetStdout() io.Writer | 184 | GetStdout() io.Writer |
374 | 185 | GetStderr() io.Writer | 185 | GetStderr() io.Writer |
375 | 186 | Infof(format string, params ...interface{}) | ||
376 | 187 | Verbosef(format string, params ...interface{}) | ||
377 | 186 | 188 | ||
378 | 187 | // InterruptNotify starts watching for interrupt signals | 189 | // InterruptNotify starts watching for interrupt signals |
379 | 188 | // on behalf of the caller, sending them to the supplied | 190 | // on behalf of the caller, sending them to the supplied |
380 | 189 | 191 | ||
381 | === modified file 'provider/common/bootstrap.go' | |||
382 | --- provider/common/bootstrap.go 2014-03-21 14:39:23 +0000 | |||
383 | +++ provider/common/bootstrap.go 2014-03-26 03:32:09 +0000 | |||
384 | @@ -42,7 +42,7 @@ | |||
385 | 42 | defer func() { handleBootstrapError(err, ctx, inst, env) }() | 42 | defer func() { handleBootstrapError(err, ctx, inst, env) }() |
386 | 43 | 43 | ||
387 | 44 | // First thing, ensure we have tools otherwise there's no point. | 44 | // First thing, ensure we have tools otherwise there's no point. |
389 | 45 | selectedTools, err := EnsureBootstrapTools(env, env.Config().DefaultSeries(), cons.Arch) | 45 | selectedTools, err := EnsureBootstrapTools(ctx, env, env.Config().DefaultSeries(), cons.Arch) |
390 | 46 | if err != nil { | 46 | if err != nil { |
391 | 47 | return err | 47 | return err |
392 | 48 | } | 48 | } |
393 | @@ -403,8 +403,8 @@ | |||
394 | 403 | // EnsureBootstrapTools finds tools, syncing with an external tools source as | 403 | // EnsureBootstrapTools finds tools, syncing with an external tools source as |
395 | 404 | // necessary; it then selects the newest tools to bootstrap with, and sets | 404 | // necessary; it then selects the newest tools to bootstrap with, and sets |
396 | 405 | // agent-version. | 405 | // agent-version. |
399 | 406 | func EnsureBootstrapTools(env environs.Environ, series string, arch *string) (coretools.List, error) { | 406 | func EnsureBootstrapTools(ctx environs.BootstrapContext, env environs.Environ, series string, arch *string) (coretools.List, error) { |
400 | 407 | possibleTools, err := bootstrap.EnsureToolsAvailability(env, series, arch) | 407 | possibleTools, err := bootstrap.EnsureToolsAvailability(ctx, env, series, arch) |
401 | 408 | if err != nil { | 408 | if err != nil { |
402 | 409 | return nil, err | 409 | return nil, err |
403 | 410 | } | 410 | } |
404 | 411 | 411 | ||
405 | === modified file 'provider/dummy/environs.go' | |||
406 | --- provider/dummy/environs.go 2014-03-18 05:03:38 +0000 | |||
407 | +++ provider/dummy/environs.go 2014-03-26 03:32:09 +0000 | |||
408 | @@ -543,7 +543,7 @@ | |||
409 | 543 | } | 543 | } |
410 | 544 | 544 | ||
411 | 545 | func (e *environ) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { | 545 | func (e *environ) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) error { |
413 | 546 | selectedTools, err := common.EnsureBootstrapTools(e, e.Config().DefaultSeries(), cons.Arch) | 546 | selectedTools, err := common.EnsureBootstrapTools(ctx, e, e.Config().DefaultSeries(), cons.Arch) |
414 | 547 | if err != nil { | 547 | if err != nil { |
415 | 548 | return err | 548 | return err |
416 | 549 | } | 549 | } |
417 | 550 | 550 | ||
418 | === modified file 'provider/local/environ.go' | |||
419 | --- provider/local/environ.go 2014-03-25 06:35:04 +0000 | |||
420 | +++ provider/local/environ.go 2014-03-26 03:32:09 +0000 | |||
421 | @@ -118,7 +118,7 @@ | |||
422 | 118 | } | 118 | } |
423 | 119 | 119 | ||
424 | 120 | vers := version.Current | 120 | vers := version.Current |
426 | 121 | selectedTools, err := common.EnsureBootstrapTools(env, vers.Series, &vers.Arch) | 121 | selectedTools, err := common.EnsureBootstrapTools(ctx, env, vers.Series, &vers.Arch) |
427 | 122 | if err != nil { | 122 | if err != nil { |
428 | 123 | return err | 123 | return err |
429 | 124 | } | 124 | } |
430 | 125 | 125 | ||
431 | === modified file 'provider/manual/environ.go' | |||
432 | --- provider/manual/environ.go 2014-03-25 13:18:46 +0000 | |||
433 | +++ provider/manual/environ.go 2014-03-26 03:32:09 +0000 | |||
434 | @@ -113,7 +113,7 @@ | |||
435 | 113 | if err != nil { | 113 | if err != nil { |
436 | 114 | return err | 114 | return err |
437 | 115 | } | 115 | } |
439 | 116 | selectedTools, err := common.EnsureBootstrapTools(e, series, hc.Arch) | 116 | selectedTools, err := common.EnsureBootstrapTools(ctx, e, series, hc.Arch) |
440 | 117 | if err != nil { | 117 | if err != nil { |
441 | 118 | return err | 118 | return err |
442 | 119 | } | 119 | } |
Reviewers: mp+212558_ code.launchpad. net,
Message:
Please take a look.
Description:
Block SIGINT during bootstrap
This change is to catch SIGINT during bootstrap
for all providers. Also, if SIGINT is delivered
while tools are being uploaded, the upload will
be cancelled.
If the user Ctrl-C's while the local provider's
bootstrap script is being executed, it will be
interrupted and will return and error to juju.
Juju will then attempt to destroy the environment
and remove the .jenv file.
We will now also send some very basic feedback to
stderr when uploading tools.
Fixes lp:1296475
https:/ /code.launchpad .net/~axwalk/ juju-core/ lp1296475- block-sigint- bootstrap/ +merge/ 212558
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/76160046/
Affected files (+192, -18 lines): bootstrap. go bootstrap/ bootstrap_ test.go bootstrap/ export_ test.go bootstrap/ interruptiblest orage.go bootstrap/ interruptiblest orage_test. go bootstrap/ synctools. go common/ bootstrap. go dummy/environs. go local/environ. go manual/ environ. go
A [revision details]
M cmd/juju/
M cmd/juju/common.go
M environs/
A environs/
A environs/
A environs/
M environs/
M provider/
M provider/
M provider/
M provider/