Merge lp:~thumper/juju-core/verify-storage-move into lp:~go-bot/juju-core/trunk
- verify-storage-move
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Tim Penhey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 1939 |
Proposed branch: | lp:~thumper/juju-core/verify-storage-move |
Merge into: | lp:~go-bot/juju-core/trunk |
Diff against target: |
478 lines (+116/-73) 10 files modified
cmd/juju/bootstrap.go (+6/-0) cmd/juju/bootstrap_test.go (+29/-8) environs/bootstrap/bootstrap.go (+10/-19) environs/emptystorage.go (+0/-25) environs/emptystorage_test.go (+2/-2) environs/jujutest/livetests.go (+6/-2) environs/jujutest/tests.go (+9/-5) environs/open.go (+30/-2) environs/open_test.go (+3/-3) provider/dummy/environs.go (+21/-7) |
To merge this branch: | bzr merge lp:~thumper/juju-core/verify-storage-move |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju Engineering | Pending | ||
Review via email: mp+188974@code.launchpad.net |
Commit message
Fail earlier when bootstrapping if up already.
I broke apart the VerifyBootstrap method into two:
EnsureNotBoot
VerifyStorage
Well I didn't create the second, just didn't call it
from the EnsureNotBootst
The ensure method is now called in the command bootstrap
prior to any upload tools checks.
However the implication of this was much futher reaching
than I expected. Many tests assumed that bootstrap.Bootstrap
was enough to bootstrap and environment, and that it would
complain if already bootstrapped. Since the complaining was
moved into the command function, some changes had to be made.
Since some of the tests were refactored to check the storage,
this required making the dummy provider actually write the
provider-state into storage like a normal provider.
I moved the VerifyStorage out of emptystorage.go and into open.go
as it seemed better fitting there (and the comment actually
makes sense now).
Also used the environs constant for the VerificationFil
Description of the change
Fail earlier when bootstrapping if up already.
I broke apart the VerifyBootstrap method into two:
EnsureNotBoot
VerifyStorage
Well I didn't create the second, just didn't call it
from the EnsureNotBootst
The ensure method is now called in the command bootstrap
prior to any upload tools checks.
However the implication of this was much futher reaching
than I expected. Many tests assumed that bootstrap.Bootstrap
was enough to bootstrap and environment, and that it would
complain if already bootstrapped. Since the complaining was
moved into the command function, some changes had to be made.
Since some of the tests were refactored to check the storage,
this required making the dummy provider actually write the
provider-state into storage like a normal provider.
I moved the VerifyStorage out of emptystorage.go and into open.go
as it seemed better fitting there (and the comment actually
makes sense now).
Also used the environs constant for the VerificationFil
Tim Penhey (thumper) wrote : | # |
Andrew Wilkins (axwalk) wrote : | # |
On 2013/10/03 03:19:01, dfc wrote:
> LGTM. Some nice boy scout work in there as well.
https:/
> File environs/
https:/
> environs/
common.
> this method hurt my head, but it makes sense in a double negative sort
of way.
LGTM
Andrew Wilkins (axwalk) wrote : | # |
https:/
File environs/
https:/
environs/
layering violation - providers
Is this TODO no longer applicable? Seems to be...
Tim Penhey (thumper) wrote : | # |
https:/
File environs/
https:/
environs/
layering violation - providers
On 2013/10/03 03:21:00, axw1 wrote:
> Is this TODO no longer applicable? Seems to be...
I took it out because everything expects it to be there.
It is how bootstrap checks to see if the environment
is already bootstrapped. I think for consistency across
providers this comment no longer makes sense.
Roger Peppe (rogpeppe) wrote : | # |
I have reservations about this.
I don't think we should be further entrenching the arbitrary way in
which the ec2 provider happened to mark its server instances.
Some thoughts below.
https:/
File environs/
https:/
environs/
layering violation - providers
On 2013/10/03 03:38:01, thumper wrote:
> On 2013/10/03 03:21:00, axw1 wrote:
> > Is this TODO no longer applicable? Seems to be...
> I took it out because everything expects it to be there.
> It is how bootstrap checks to see if the environment
> is already bootstrapped. I think for consistency across
> providers this comment no longer makes sense.
The comment *is* still relevant IMHO. It has long been
the plan to move away from using file storage on ec2 to
indicate state servers (it could be done nicely with
instance tags) and therefore allow the possibility of
having an environment with no local storage at all.
It is *currently* how bootstrap checks to see if the
environment is already bootstrapped, but there's no
particular necessity for it to be this way.
It would be better to have an Environ.
call instead.
https:/
File environs/
https:/
environs/
environs.Environ) error {
We usually use the word "ensure" to mean "ensure that something is so".
That's not the case here.
I'd prefer:
// IsBootstrapped returns whether the environment is
// bootstrapped.
func IsBootstrapped(env environs.Environ) (bool, error)
https:/
File environs/open.go (right):
https:/
environs/
s/ error//
Preview Diff
1 | === modified file 'cmd/juju/bootstrap.go' | |||
2 | --- cmd/juju/bootstrap.go 2013-10-02 05:18:02 +0000 | |||
3 | +++ cmd/juju/bootstrap.go 2013-10-03 03:45:40 +0000 | |||
4 | @@ -78,6 +78,12 @@ | |||
5 | 78 | return fmt.Errorf("failed to enable bootstrap storage: %v", err) | 78 | return fmt.Errorf("failed to enable bootstrap storage: %v", err) |
6 | 79 | } | 79 | } |
7 | 80 | } | 80 | } |
8 | 81 | // Check to see if the environment is already bootstrapped | ||
9 | 82 | // before potentially uploading any tools. | ||
10 | 83 | if err := bootstrap.EnsureNotBootstrapped(environ); err != nil { | ||
11 | 84 | return err | ||
12 | 85 | } | ||
13 | 86 | |||
14 | 81 | // TODO (wallyworld): 2013-09-20 bug 1227931 | 87 | // TODO (wallyworld): 2013-09-20 bug 1227931 |
15 | 82 | // We can set a custom tools data source instead of doing an | 88 | // We can set a custom tools data source instead of doing an |
16 | 83 | // unecessary upload. | 89 | // unecessary upload. |
17 | 84 | 90 | ||
18 | === modified file 'cmd/juju/bootstrap_test.go' | |||
19 | --- cmd/juju/bootstrap_test.go 2013-10-02 18:34:59 +0000 | |||
20 | +++ cmd/juju/bootstrap_test.go 2013-10-03 03:45:40 +0000 | |||
21 | @@ -53,6 +53,13 @@ | |||
22 | 53 | s.LoggingSuite.TearDownSuite(c) | 53 | s.LoggingSuite.TearDownSuite(c) |
23 | 54 | } | 54 | } |
24 | 55 | 55 | ||
25 | 56 | func (s *BootstrapSuite) TearDownTest(c *gc.C) { | ||
26 | 57 | s.ToolsFixture.TearDownTest(c) | ||
27 | 58 | s.MgoSuite.TearDownTest(c) | ||
28 | 59 | s.LoggingSuite.TearDownTest(c) | ||
29 | 60 | dummy.Reset() | ||
30 | 61 | } | ||
31 | 62 | |||
32 | 56 | type bootstrapRetryTest struct { | 63 | type bootstrapRetryTest struct { |
33 | 57 | info string | 64 | info string |
34 | 58 | args []string | 65 | args []string |
35 | @@ -101,13 +108,6 @@ | |||
36 | 101 | c.Assert(err, gc.ErrorMatches, test.err) | 108 | c.Assert(err, gc.ErrorMatches, test.err) |
37 | 102 | } | 109 | } |
38 | 103 | 110 | ||
39 | 104 | func (s *BootstrapSuite) TearDownTest(c *gc.C) { | ||
40 | 105 | s.ToolsFixture.TearDownTest(c) | ||
41 | 106 | s.MgoSuite.TearDownTest(c) | ||
42 | 107 | s.LoggingSuite.TearDownTest(c) | ||
43 | 108 | dummy.Reset() | ||
44 | 109 | } | ||
45 | 110 | |||
46 | 111 | func (s *BootstrapSuite) TestTest(c *gc.C) { | 111 | func (s *BootstrapSuite) TestTest(c *gc.C) { |
47 | 112 | uploadTools = mockUploadTools | 112 | uploadTools = mockUploadTools |
48 | 113 | defer func() { uploadTools = sync.Upload }() | 113 | defer func() { uploadTools = sync.Upload }() |
49 | @@ -190,7 +190,11 @@ | |||
50 | 190 | } | 190 | } |
51 | 191 | opPutBootstrapVerifyFile := (<-opc).(dummy.OpPutFile) | 191 | opPutBootstrapVerifyFile := (<-opc).(dummy.OpPutFile) |
52 | 192 | c.Check(opPutBootstrapVerifyFile.Env, gc.Equals, "peckham") | 192 | c.Check(opPutBootstrapVerifyFile.Env, gc.Equals, "peckham") |
54 | 193 | c.Check(opPutBootstrapVerifyFile.FileName, gc.Equals, "bootstrap-verify") | 193 | c.Check(opPutBootstrapVerifyFile.FileName, gc.Equals, environs.VerificationFilename) |
55 | 194 | |||
56 | 195 | opPutBootstrapInitFile := (<-opc).(dummy.OpPutFile) | ||
57 | 196 | c.Check(opPutBootstrapInitFile.Env, gc.Equals, "peckham") | ||
58 | 197 | c.Check(opPutBootstrapInitFile.FileName, gc.Equals, "provider-state") | ||
59 | 194 | 198 | ||
60 | 195 | opBootstrap := (<-opc).(dummy.OpBootstrap) | 199 | opBootstrap := (<-opc).(dummy.OpBootstrap) |
61 | 196 | c.Check(opBootstrap.Env, gc.Equals, "peckham") | 200 | c.Check(opBootstrap.Env, gc.Equals, "peckham") |
62 | @@ -265,6 +269,23 @@ | |||
63 | 265 | }, | 269 | }, |
64 | 266 | }} | 270 | }} |
65 | 267 | 271 | ||
66 | 272 | func (s *BootstrapSuite) TestBootstrapTwice(c *gc.C) { | ||
67 | 273 | restore := createToolsStore(c) | ||
68 | 274 | defer restore() | ||
69 | 275 | _, fake := makeEmptyFakeHome(c) | ||
70 | 276 | defer fake.Restore() | ||
71 | 277 | |||
72 | 278 | ctx := coretesting.Context(c) | ||
73 | 279 | code := cmd.Main(&BootstrapCommand{}, ctx, nil) | ||
74 | 280 | c.Check(code, gc.Equals, 0) | ||
75 | 281 | |||
76 | 282 | ctx2 := coretesting.Context(c) | ||
77 | 283 | code2 := cmd.Main(&BootstrapCommand{}, ctx2, nil) | ||
78 | 284 | c.Check(code2, gc.Equals, 1) | ||
79 | 285 | c.Check(coretesting.Stderr(ctx2), gc.Equals, "error: environment is already bootstrapped\n") | ||
80 | 286 | c.Check(coretesting.Stdout(ctx2), gc.Equals, "") | ||
81 | 287 | } | ||
82 | 288 | |||
83 | 268 | func (s *BootstrapSuite) TestAutoSync(c *gc.C) { | 289 | func (s *BootstrapSuite) TestAutoSync(c *gc.C) { |
84 | 269 | // Prepare a mock storage for testing and store the | 290 | // Prepare a mock storage for testing and store the |
85 | 270 | // dummy tools in there. | 291 | // dummy tools in there. |
86 | 271 | 292 | ||
87 | === modified file 'environs/bootstrap/bootstrap.go' | |||
88 | --- environs/bootstrap/bootstrap.go 2013-10-02 10:39:12 +0000 | |||
89 | +++ environs/bootstrap/bootstrap.go 2013-10-03 03:45:40 +0000 | |||
90 | @@ -42,11 +42,8 @@ | |||
91 | 42 | if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { | 42 | if _, hasCAKey := cfg.CAPrivateKey(); !hasCAKey { |
92 | 43 | return fmt.Errorf("environment configuration has no ca-private-key") | 43 | return fmt.Errorf("environment configuration has no ca-private-key") |
93 | 44 | } | 44 | } |
99 | 45 | // If the state file exists, it might actually have just been | 45 | // Write out the bootstrap-init file, and confirm storage is writeable. |
100 | 46 | // removed by Destroy, and eventual consistency has not caught | 46 | if err := environs.VerifyStorage(environ.Storage()); err != nil { |
96 | 47 | // up yet, so we retry to verify if that is happening. | ||
97 | 48 | err := verifyBootstrapInit(environ) | ||
98 | 49 | if err != nil { | ||
101 | 50 | return err | 47 | return err |
102 | 51 | } | 48 | } |
103 | 52 | 49 | ||
104 | @@ -89,24 +86,18 @@ | |||
105 | 89 | return environ.Bootstrap(cons, newestTools) | 86 | return environ.Bootstrap(cons, newestTools) |
106 | 90 | } | 87 | } |
107 | 91 | 88 | ||
118 | 92 | // verifyBootstrapInit does the common initial check before bootstrapping, to | 89 | // EnsureNotBootstrapped returns null if the environment is not bootstrapped, |
119 | 93 | // confirm that the environment isn't already running, and that the storage | 90 | // and an error if it is or if the function was not able to tell. |
120 | 94 | // works. | 91 | func EnsureNotBootstrapped(env environs.Environ) error { |
121 | 95 | func verifyBootstrapInit(env environs.Environ) error { | 92 | _, err := common.LoadState(env.Storage()) |
122 | 96 | // TODO(rog) this feels like a layering violation - providers | 93 | // If there is no error loading the bootstrap state, then we are bootstrapped. |
113 | 97 | // should not necessarily be required to store their bootstrap | ||
114 | 98 | // state in a file. This verification should probably | ||
115 | 99 | // be moved into provider and called by the providers themselves. | ||
116 | 100 | stor := env.Storage() | ||
117 | 101 | _, err := common.LoadState(stor) | ||
123 | 102 | if err == nil { | 94 | if err == nil { |
124 | 103 | return fmt.Errorf("environment is already bootstrapped") | 95 | return fmt.Errorf("environment is already bootstrapped") |
125 | 104 | } | 96 | } |
128 | 105 | if err != environs.ErrNotBootstrapped { | 97 | if err == environs.ErrNotBootstrapped { |
129 | 106 | return fmt.Errorf("cannot query old bootstrap state: %v", err) | 98 | return nil |
130 | 107 | } | 99 | } |
133 | 108 | 100 | return err | |
132 | 109 | return environs.VerifyStorage(stor) | ||
134 | 110 | } | 101 | } |
135 | 111 | 102 | ||
136 | 112 | // ConfigureBootstrapMachine adds the initial machine into state. As a part | 103 | // ConfigureBootstrapMachine adds the initial machine into state. As a part |
137 | 113 | 104 | ||
138 | === modified file 'environs/emptystorage.go' | |||
139 | --- environs/emptystorage.go 2013-09-18 07:13:09 +0000 | |||
140 | +++ environs/emptystorage.go 2013-10-03 03:45:40 +0000 | |||
141 | @@ -6,11 +6,9 @@ | |||
142 | 6 | import ( | 6 | import ( |
143 | 7 | "fmt" | 7 | "fmt" |
144 | 8 | "io" | 8 | "io" |
145 | 9 | "strings" | ||
146 | 10 | 9 | ||
147 | 11 | "launchpad.net/juju-core/environs/storage" | 10 | "launchpad.net/juju-core/environs/storage" |
148 | 12 | "launchpad.net/juju-core/errors" | 11 | "launchpad.net/juju-core/errors" |
149 | 13 | "launchpad.net/juju-core/log" | ||
150 | 14 | "launchpad.net/juju-core/utils" | 12 | "launchpad.net/juju-core/utils" |
151 | 15 | ) | 13 | ) |
152 | 16 | 14 | ||
153 | @@ -20,16 +18,6 @@ | |||
154 | 20 | 18 | ||
155 | 21 | type emptyStorage struct{} | 19 | type emptyStorage struct{} |
156 | 22 | 20 | ||
157 | 23 | // File named `verificationFilename` in the storage will contain | ||
158 | 24 | // `verificationContent`. This is also used to differentiate between | ||
159 | 25 | // Python Juju and juju-core environments, so change the content with | ||
160 | 26 | // care (and update CheckEnvironment below when you do that). | ||
161 | 27 | const verificationFilename string = "bootstrap-verify" | ||
162 | 28 | const verificationContent = "juju-core storage writing verified: ok\n" | ||
163 | 29 | |||
164 | 30 | var VerifyStorageError error = fmt.Errorf( | ||
165 | 31 | "provider storage is not writable") | ||
166 | 32 | |||
167 | 33 | func (s emptyStorage) Get(name string) (io.ReadCloser, error) { | 21 | func (s emptyStorage) Get(name string) (io.ReadCloser, error) { |
168 | 34 | return nil, errors.NotFoundf("file %q", name) | 22 | return nil, errors.NotFoundf("file %q", name) |
169 | 35 | } | 23 | } |
170 | @@ -51,16 +39,3 @@ | |||
171 | 51 | func (s emptyStorage) ShouldRetry(err error) bool { | 39 | func (s emptyStorage) ShouldRetry(err error) bool { |
172 | 52 | return false | 40 | return false |
173 | 53 | } | 41 | } |
174 | 54 | |||
175 | 55 | func VerifyStorage(stor storage.Storage) error { | ||
176 | 56 | reader := strings.NewReader(verificationContent) | ||
177 | 57 | err := stor.Put(verificationFilename, reader, | ||
178 | 58 | int64(len(verificationContent))) | ||
179 | 59 | if err != nil { | ||
180 | 60 | log.Debugf( | ||
181 | 61 | "environs: failed to write bootstrap-verify file: %v", | ||
182 | 62 | err) | ||
183 | 63 | return VerifyStorageError | ||
184 | 64 | } | ||
185 | 65 | return nil | ||
186 | 66 | } | ||
187 | 67 | 42 | ||
188 | === modified file 'environs/emptystorage_test.go' | |||
189 | --- environs/emptystorage_test.go 2013-10-01 16:53:02 +0000 | |||
190 | +++ environs/emptystorage_test.go 2013-10-03 03:45:40 +0000 | |||
191 | @@ -68,7 +68,7 @@ | |||
192 | 68 | stor := environ.Storage() | 68 | stor := environ.Storage() |
193 | 69 | err = environs.VerifyStorage(stor) | 69 | err = environs.VerifyStorage(stor) |
194 | 70 | c.Assert(err, gc.IsNil) | 70 | c.Assert(err, gc.IsNil) |
196 | 71 | reader, err := storage.Get(stor, "bootstrap-verify") | 71 | reader, err := storage.Get(stor, environs.VerificationFilename) |
197 | 72 | c.Assert(err, gc.IsNil) | 72 | c.Assert(err, gc.IsNil) |
198 | 73 | defer reader.Close() | 73 | defer reader.Close() |
199 | 74 | contents, err := ioutil.ReadAll(reader) | 74 | contents, err := ioutil.ReadAll(reader) |
200 | @@ -84,7 +84,7 @@ | |||
201 | 84 | c.Assert(err, gc.IsNil) | 84 | c.Assert(err, gc.IsNil) |
202 | 85 | stor := environ.Storage() | 85 | stor := environ.Storage() |
203 | 86 | someError := errors.Unauthorizedf("you shall not pass") | 86 | someError := errors.Unauthorizedf("you shall not pass") |
205 | 87 | dummy.Poison(stor, "bootstrap-verify", someError) | 87 | dummy.Poison(stor, environs.VerificationFilename, someError) |
206 | 88 | err = environs.VerifyStorage(stor) | 88 | err = environs.VerifyStorage(stor) |
207 | 89 | c.Assert(err, gc.Equals, environs.VerifyStorageError) | 89 | c.Assert(err, gc.Equals, environs.VerifyStorageError) |
208 | 90 | } | 90 | } |
209 | 91 | 91 | ||
210 | === modified file 'environs/jujutest/livetests.go' | |||
211 | --- environs/jujutest/livetests.go 2013-10-02 10:39:12 +0000 | |||
212 | +++ environs/jujutest/livetests.go 2013-10-03 03:45:40 +0000 | |||
213 | @@ -137,7 +137,9 @@ | |||
214 | 137 | c.Assert(err, gc.IsNil) | 137 | c.Assert(err, gc.IsNil) |
215 | 138 | } | 138 | } |
216 | 139 | envtesting.UploadFakeTools(c, t.Env.Storage()) | 139 | envtesting.UploadFakeTools(c, t.Env.Storage()) |
218 | 140 | err := bootstrap.Bootstrap(t.Env, cons) | 140 | err := bootstrap.EnsureNotBootstrapped(t.Env) |
219 | 141 | c.Assert(err, gc.IsNil) | ||
220 | 142 | err = bootstrap.Bootstrap(t.Env, cons) | ||
221 | 141 | c.Assert(err, gc.IsNil) | 143 | c.Assert(err, gc.IsNil) |
222 | 142 | t.bootstrapped = true | 144 | t.bootstrapped = true |
223 | 143 | } | 145 | } |
224 | @@ -385,9 +387,11 @@ | |||
225 | 385 | } | 387 | } |
226 | 386 | 388 | ||
227 | 387 | func (t *LiveTests) TestBootstrapMultiple(c *gc.C) { | 389 | func (t *LiveTests) TestBootstrapMultiple(c *gc.C) { |
228 | 390 | // bootstrap.Bootstrap no longer raises errors if the environment is | ||
229 | 391 | // already up, this has been moved into the bootstrap command. | ||
230 | 388 | t.BootstrapOnce(c) | 392 | t.BootstrapOnce(c) |
231 | 389 | 393 | ||
233 | 390 | err := bootstrap.Bootstrap(t.Env, constraints.Value{}) | 394 | err := bootstrap.EnsureNotBootstrapped(t.Env) |
234 | 391 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") | 395 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") |
235 | 392 | 396 | ||
236 | 393 | c.Logf("destroy env") | 397 | c.Logf("destroy env") |
237 | 394 | 398 | ||
238 | === modified file 'environs/jujutest/tests.go' | |||
239 | --- environs/jujutest/tests.go 2013-10-01 12:03:33 +0000 | |||
240 | +++ environs/jujutest/tests.go 2013-10-03 03:45:40 +0000 | |||
241 | @@ -131,19 +131,21 @@ | |||
242 | 131 | func (t *Tests) TestBootstrap(c *gc.C) { | 131 | func (t *Tests) TestBootstrap(c *gc.C) { |
243 | 132 | e := t.Prepare(c) | 132 | e := t.Prepare(c) |
244 | 133 | envtesting.UploadFakeTools(c, e.Storage()) | 133 | envtesting.UploadFakeTools(c, e.Storage()) |
246 | 134 | err := bootstrap.Bootstrap(e, constraints.Value{}) | 134 | err := bootstrap.EnsureNotBootstrapped(e) |
247 | 135 | c.Assert(err, gc.IsNil) | ||
248 | 136 | err = bootstrap.Bootstrap(e, constraints.Value{}) | ||
249 | 135 | c.Assert(err, gc.IsNil) | 137 | c.Assert(err, gc.IsNil) |
250 | 136 | 138 | ||
251 | 137 | info, apiInfo, err := e.StateInfo() | 139 | info, apiInfo, err := e.StateInfo() |
252 | 138 | c.Check(info.Addrs, gc.Not(gc.HasLen), 0) | 140 | c.Check(info.Addrs, gc.Not(gc.HasLen), 0) |
253 | 139 | c.Check(apiInfo.Addrs, gc.Not(gc.HasLen), 0) | 141 | c.Check(apiInfo.Addrs, gc.Not(gc.HasLen), 0) |
254 | 140 | 142 | ||
256 | 141 | err = bootstrap.Bootstrap(e, constraints.Value{}) | 143 | err = bootstrap.EnsureNotBootstrapped(e) |
257 | 142 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") | 144 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") |
258 | 143 | 145 | ||
259 | 144 | e2 := t.Open(c) | 146 | e2 := t.Open(c) |
260 | 145 | envtesting.UploadFakeTools(c, e2.Storage()) | 147 | envtesting.UploadFakeTools(c, e2.Storage()) |
262 | 146 | err = bootstrap.Bootstrap(e2, constraints.Value{}) | 148 | err = bootstrap.EnsureNotBootstrapped(e2) |
263 | 147 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") | 149 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") |
264 | 148 | 150 | ||
265 | 149 | info2, apiInfo2, err := e2.StateInfo() | 151 | info2, apiInfo2, err := e2.StateInfo() |
266 | @@ -157,11 +159,13 @@ | |||
267 | 157 | e3 := t.Prepare(c) | 159 | e3 := t.Prepare(c) |
268 | 158 | envtesting.UploadFakeTools(c, e3.Storage()) | 160 | envtesting.UploadFakeTools(c, e3.Storage()) |
269 | 159 | 161 | ||
270 | 162 | err = bootstrap.EnsureNotBootstrapped(e3) | ||
271 | 163 | c.Assert(err, gc.IsNil) | ||
272 | 160 | err = bootstrap.Bootstrap(e3, constraints.Value{}) | 164 | err = bootstrap.Bootstrap(e3, constraints.Value{}) |
273 | 161 | c.Assert(err, gc.IsNil) | 165 | c.Assert(err, gc.IsNil) |
274 | 162 | 166 | ||
277 | 163 | err = bootstrap.Bootstrap(e3, constraints.Value{}) | 167 | err = bootstrap.EnsureNotBootstrapped(e3) |
278 | 164 | c.Assert(err, gc.NotNil) | 168 | c.Assert(err, gc.ErrorMatches, "environment is already bootstrapped") |
279 | 165 | } | 169 | } |
280 | 166 | 170 | ||
281 | 167 | var noRetry = utils.AttemptStrategy{} | 171 | var noRetry = utils.AttemptStrategy{} |
282 | 168 | 172 | ||
283 | === modified file 'environs/open.go' | |||
284 | --- environs/open.go 2013-10-02 18:41:54 +0000 | |||
285 | +++ environs/open.go 2013-10-03 03:45:40 +0000 | |||
286 | @@ -6,6 +6,7 @@ | |||
287 | 6 | import ( | 6 | import ( |
288 | 7 | "fmt" | 7 | "fmt" |
289 | 8 | "io/ioutil" | 8 | "io/ioutil" |
290 | 9 | "strings" | ||
291 | 9 | "time" | 10 | "time" |
292 | 10 | 11 | ||
293 | 11 | "launchpad.net/juju-core/cert" | 12 | "launchpad.net/juju-core/cert" |
294 | @@ -15,7 +16,21 @@ | |||
295 | 15 | "launchpad.net/juju-core/errors" | 16 | "launchpad.net/juju-core/errors" |
296 | 16 | ) | 17 | ) |
297 | 17 | 18 | ||
299 | 18 | var InvalidEnvironmentError = fmt.Errorf("environment is not a juju-core environment") | 19 | // File named `VerificationFilename` in the storage will contain |
300 | 20 | // `verificationContent`. This is also used to differentiate between | ||
301 | 21 | // Python Juju and juju-core environments, so change the content with | ||
302 | 22 | // care (and update CheckEnvironment below when you do that). | ||
303 | 23 | const ( | ||
304 | 24 | VerificationFilename = "bootstrap-verify" | ||
305 | 25 | verificationContent = "juju-core storage writing verified: ok\n" | ||
306 | 26 | ) | ||
307 | 27 | |||
308 | 28 | var ( | ||
309 | 29 | VerifyStorageError error = fmt.Errorf( | ||
310 | 30 | "provider storage is not writable") | ||
311 | 31 | InvalidEnvironmentError = fmt.Errorf( | ||
312 | 32 | "environment is not a juju-core environment") | ||
313 | 33 | ) | ||
314 | 19 | 34 | ||
315 | 20 | // ConfigForName returns the configuration for the environment with the | 35 | // ConfigForName returns the configuration for the environment with the |
316 | 21 | // given name from the default environments file. If the name is blank, | 36 | // given name from the default environments file. If the name is blank, |
317 | @@ -199,6 +214,19 @@ | |||
318 | 199 | return nil | 214 | return nil |
319 | 200 | } | 215 | } |
320 | 201 | 216 | ||
321 | 217 | // VerifyStorage writes the bootstrap init file to the storage to indicate | ||
322 | 218 | // that the storage is writable. | ||
323 | 219 | func VerifyStorage(stor storage.Storage) error { | ||
324 | 220 | reader := strings.NewReader(verificationContent) | ||
325 | 221 | err := stor.Put(VerificationFilename, reader, | ||
326 | 222 | int64(len(verificationContent))) | ||
327 | 223 | if err != nil { | ||
328 | 224 | logger.Warningf("failed to write bootstrap-verify file: %v", err) | ||
329 | 225 | return VerifyStorageError | ||
330 | 226 | } | ||
331 | 227 | return nil | ||
332 | 228 | } | ||
333 | 229 | |||
334 | 202 | // CheckEnvironment checks if an environment has a bootstrap-verify | 230 | // CheckEnvironment checks if an environment has a bootstrap-verify |
335 | 203 | // that is written by juju-core commands (as compared to one being | 231 | // that is written by juju-core commands (as compared to one being |
336 | 204 | // written by Python juju). | 232 | // written by Python juju). |
337 | @@ -210,7 +238,7 @@ | |||
338 | 210 | // Returns InvalidEnvironmentError on failure, nil otherwise. | 238 | // Returns InvalidEnvironmentError on failure, nil otherwise. |
339 | 211 | func CheckEnvironment(environ Environ) error { | 239 | func CheckEnvironment(environ Environ) error { |
340 | 212 | stor := environ.Storage() | 240 | stor := environ.Storage() |
342 | 213 | reader, err := storage.Get(stor, verificationFilename) | 241 | reader, err := storage.Get(stor, VerificationFilename) |
343 | 214 | if errors.IsNotFoundError(err) { | 242 | if errors.IsNotFoundError(err) { |
344 | 215 | // When verification file does not exist, this is a juju-core | 243 | // When verification file does not exist, this is a juju-core |
345 | 216 | // environment. | 244 | // environment. |
346 | 217 | 245 | ||
347 | === modified file 'environs/open_test.go' | |||
348 | --- environs/open_test.go 2013-10-02 18:41:54 +0000 | |||
349 | +++ environs/open_test.go 2013-10-03 03:45:40 +0000 | |||
350 | @@ -297,7 +297,7 @@ | |||
351 | 297 | // When the bootstrap-verify file does not exist, it still believes | 297 | // When the bootstrap-verify file does not exist, it still believes |
352 | 298 | // the environment is a juju-core one because earlier versions | 298 | // the environment is a juju-core one because earlier versions |
353 | 299 | // did not create that file. | 299 | // did not create that file. |
355 | 300 | err = stor.Remove("bootstrap-verify") | 300 | err = stor.Remove(environs.VerificationFilename) |
356 | 301 | c.Assert(err, gc.IsNil) | 301 | c.Assert(err, gc.IsNil) |
357 | 302 | err = environs.CheckEnvironment(environ) | 302 | err = environs.CheckEnvironment(environ) |
358 | 303 | c.Assert(err, gc.IsNil) | 303 | c.Assert(err, gc.IsNil) |
359 | @@ -318,7 +318,7 @@ | |||
360 | 318 | // When fetching the verification file from storage fails, | 318 | // When fetching the verification file from storage fails, |
361 | 319 | // we get an InvalidEnvironmentError. | 319 | // we get an InvalidEnvironmentError. |
362 | 320 | someError := errors.Unauthorizedf("you shall not pass") | 320 | someError := errors.Unauthorizedf("you shall not pass") |
364 | 321 | dummy.Poison(stor, "bootstrap-verify", someError) | 321 | dummy.Poison(stor, environs.VerificationFilename, someError) |
365 | 322 | err = environs.CheckEnvironment(environ) | 322 | err = environs.CheckEnvironment(environ) |
366 | 323 | c.Assert(err, gc.Equals, someError) | 323 | c.Assert(err, gc.Equals, someError) |
367 | 324 | } | 324 | } |
368 | @@ -333,7 +333,7 @@ | |||
369 | 333 | stor := environ.Storage() | 333 | stor := environ.Storage() |
370 | 334 | content := "bad verification content" | 334 | content := "bad verification content" |
371 | 335 | reader := strings.NewReader(content) | 335 | reader := strings.NewReader(content) |
373 | 336 | err = stor.Put("bootstrap-verify", reader, int64(len(content))) | 336 | err = stor.Put(environs.VerificationFilename, reader, int64(len(content))) |
374 | 337 | c.Assert(err, gc.IsNil) | 337 | c.Assert(err, gc.IsNil) |
375 | 338 | 338 | ||
376 | 339 | // When the bootstrap-verify file contains unexpected content, | 339 | // When the bootstrap-verify file contains unexpected content, |
377 | 340 | 340 | ||
378 | === modified file 'provider/dummy/environs.go' | |||
379 | --- provider/dummy/environs.go 2013-10-03 00:40:14 +0000 | |||
380 | +++ provider/dummy/environs.go 2013-10-03 03:45:40 +0000 | |||
381 | @@ -33,6 +33,8 @@ | |||
382 | 33 | "sync" | 33 | "sync" |
383 | 34 | "time" | 34 | "time" |
384 | 35 | 35 | ||
385 | 36 | "launchpad.net/loggo" | ||
386 | 37 | |||
387 | 36 | "launchpad.net/juju-core/constraints" | 38 | "launchpad.net/juju-core/constraints" |
388 | 37 | "launchpad.net/juju-core/environs" | 39 | "launchpad.net/juju-core/environs" |
389 | 38 | "launchpad.net/juju-core/environs/cloudinit" | 40 | "launchpad.net/juju-core/environs/cloudinit" |
390 | @@ -42,7 +44,6 @@ | |||
391 | 42 | "launchpad.net/juju-core/environs/storage" | 44 | "launchpad.net/juju-core/environs/storage" |
392 | 43 | "launchpad.net/juju-core/environs/tools" | 45 | "launchpad.net/juju-core/environs/tools" |
393 | 44 | "launchpad.net/juju-core/instance" | 46 | "launchpad.net/juju-core/instance" |
394 | 45 | "launchpad.net/juju-core/log" | ||
395 | 46 | "launchpad.net/juju-core/names" | 47 | "launchpad.net/juju-core/names" |
396 | 47 | "launchpad.net/juju-core/provider" | 48 | "launchpad.net/juju-core/provider" |
397 | 48 | "launchpad.net/juju-core/provider/common" | 49 | "launchpad.net/juju-core/provider/common" |
398 | @@ -55,6 +56,8 @@ | |||
399 | 55 | "launchpad.net/juju-core/utils" | 56 | "launchpad.net/juju-core/utils" |
400 | 56 | ) | 57 | ) |
401 | 57 | 58 | ||
402 | 59 | var logger = loggo.GetLogger("juju.provider.dummy") | ||
403 | 60 | |||
404 | 58 | // SampleConfig() returns an environment configuration with all required | 61 | // SampleConfig() returns an environment configuration with all required |
405 | 59 | // attributes set. | 62 | // attributes set. |
406 | 60 | func SampleConfig() testing.Attrs { | 63 | func SampleConfig() testing.Attrs { |
407 | @@ -208,7 +211,7 @@ | |||
408 | 208 | // operation listener. All opened environments after Reset will share | 211 | // operation listener. All opened environments after Reset will share |
409 | 209 | // the same underlying state. | 212 | // the same underlying state. |
410 | 210 | func Reset() { | 213 | func Reset() { |
412 | 211 | log.Infof("environs/dummy: reset environment") | 214 | logger.Infof("reset environment") |
413 | 212 | p := &providerInstance | 215 | p := &providerInstance |
414 | 213 | p.mu.Lock() | 216 | p.mu.Lock() |
415 | 214 | defer p.mu.Unlock() | 217 | defer p.mu.Unlock() |
416 | @@ -538,7 +541,7 @@ | |||
417 | 538 | return fmt.Errorf("no CA certificate in environment configuration") | 541 | return fmt.Errorf("no CA certificate in environment configuration") |
418 | 539 | } | 542 | } |
419 | 540 | 543 | ||
421 | 541 | log.Infof("environs/dummy: would pick tools from %s", possibleTools) | 544 | logger.Infof("would pick tools from %s", possibleTools) |
422 | 542 | cfg, err := environs.BootstrapConfig(e.Config()) | 545 | cfg, err := environs.BootstrapConfig(e.Config()) |
423 | 543 | if err != nil { | 546 | if err != nil { |
424 | 544 | return fmt.Errorf("cannot make bootstrap config: %v", err) | 547 | return fmt.Errorf("cannot make bootstrap config: %v", err) |
425 | @@ -553,6 +556,17 @@ | |||
426 | 553 | if estate.bootstrapped { | 556 | if estate.bootstrapped { |
427 | 554 | return fmt.Errorf("environment is already bootstrapped") | 557 | return fmt.Errorf("environment is already bootstrapped") |
428 | 555 | } | 558 | } |
429 | 559 | // Write the bootstrap file just like a normal provider. However | ||
430 | 560 | // we need to release the mutex for the save state to work, so regain | ||
431 | 561 | // it after the call. | ||
432 | 562 | estate.mu.Unlock() | ||
433 | 563 | if err := common.SaveState(e.Storage(), &common.BootstrapState{StateInstances: []instance.Id{"localhost"}}); err != nil { | ||
434 | 564 | logger.Errorf("failed to save state instances: %v", err) | ||
435 | 565 | estate.mu.Lock() // otherwise defered unlock will fail | ||
436 | 566 | return err | ||
437 | 567 | } | ||
438 | 568 | estate.mu.Lock() // back at it | ||
439 | 569 | |||
440 | 556 | if e.ecfg().stateServer() { | 570 | if e.ecfg().stateServer() { |
441 | 557 | // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go | 571 | // TODO(rog) factor out relevant code from cmd/jujud/bootstrap.go |
442 | 558 | // so that we can call it here. | 572 | // so that we can call it here. |
443 | @@ -653,7 +667,7 @@ | |||
444 | 653 | 667 | ||
445 | 654 | defer delay() | 668 | defer delay() |
446 | 655 | machineId := machineConfig.MachineId | 669 | machineId := machineConfig.MachineId |
448 | 656 | log.Infof("environs/dummy: dummy startinstance, machine %s", machineId) | 670 | logger.Infof("dummy startinstance, machine %s", machineId) |
449 | 657 | if err := e.checkBroken("StartInstance"); err != nil { | 671 | if err := e.checkBroken("StartInstance"); err != nil { |
450 | 658 | return nil, nil, err | 672 | return nil, nil, err |
451 | 659 | } | 673 | } |
452 | @@ -675,7 +689,7 @@ | |||
453 | 675 | if machineConfig.APIInfo.Tag != names.MachineTag(machineId) { | 689 | if machineConfig.APIInfo.Tag != names.MachineTag(machineId) { |
454 | 676 | return nil, nil, fmt.Errorf("entity tag must match started machine") | 690 | return nil, nil, fmt.Errorf("entity tag must match started machine") |
455 | 677 | } | 691 | } |
457 | 678 | log.Infof("environs/dummy: would pick tools from %s", possibleTools) | 692 | logger.Infof("would pick tools from %s", possibleTools) |
458 | 679 | series := possibleTools.OneSeries() | 693 | series := possibleTools.OneSeries() |
459 | 680 | i := &dummyInstance{ | 694 | i := &dummyInstance{ |
460 | 681 | id: instance.Id(fmt.Sprintf("%s-%d", e.name, estate.maxId)), | 695 | id: instance.Id(fmt.Sprintf("%s-%d", e.name, estate.maxId)), |
461 | @@ -899,7 +913,7 @@ | |||
462 | 899 | 913 | ||
463 | 900 | func (inst *dummyInstance) OpenPorts(machineId string, ports []instance.Port) error { | 914 | func (inst *dummyInstance) OpenPorts(machineId string, ports []instance.Port) error { |
464 | 901 | defer delay() | 915 | defer delay() |
466 | 902 | log.Infof("environs/dummy: openPorts %s, %#v", machineId, ports) | 916 | logger.Infof("openPorts %s, %#v", machineId, ports) |
467 | 903 | if inst.firewallMode != config.FwInstance { | 917 | if inst.firewallMode != config.FwInstance { |
468 | 904 | return fmt.Errorf("invalid firewall mode %q for opening ports on instance", | 918 | return fmt.Errorf("invalid firewall mode %q for opening ports on instance", |
469 | 905 | inst.firewallMode) | 919 | inst.firewallMode) |
470 | @@ -970,7 +984,7 @@ | |||
471 | 970 | // pause execution to simulate the latency of a real provider | 984 | // pause execution to simulate the latency of a real provider |
472 | 971 | func delay() { | 985 | func delay() { |
473 | 972 | if providerDelay > 0 { | 986 | if providerDelay > 0 { |
475 | 973 | log.Infof("environs/dummy: pausing for %v", providerDelay) | 987 | logger.Infof("pausing for %v", providerDelay) |
476 | 974 | <-time.After(providerDelay) | 988 | <-time.After(providerDelay) |
477 | 975 | } | 989 | } |
478 | 976 | } | 990 | } |
Reviewers: mp+188974_ code.launchpad. net,
Message:
Please take a look.
Description:
Fail earlier when bootstrapping if up already.
I broke apart the VerifyBootstrap method into two: tstrapped rapped call.
EnsureNotBoo
VerifyStorage
Well I didn't create the second, just didn't call it
from the EnsureNotBootst
The ensure method is now called in the command bootstrap
prior to any upload tools checks.
However the implication of this was much futher reaching
than I expected. Many tests assumed that bootstrap.Bootstrap
was enough to bootstrap and environment, and that it would
complain if already bootstrapped. Since the complaining was
moved into the command function, some changes had to be made.
Since some of the tests were refactored to check the storage,
this required making the dummy provider actually write the
provider-state into storage like a normal provider.
I moved the VerifyStorage out of emptystorage.go and into open.go
as it seemed better fitting there (and the comment actually
makes sense now).
Also used the environs constant for the VerificationFil ename.
https:/ /code.launchpad .net/~thumper/ juju-core/ verify- storage- move/+merge/ 188974
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/14279044/
Affected files (+119, -74 lines): bootstrap. go bootstrap_ test.go bootstrap/ bootstrap. go emptystorage. go emptystorage_ test.go jujutest/ livetests. go jujutest/ tests.go open_test. go dummy/environs. go
A [revision details]
M cmd/juju/
M cmd/juju/
M environs/
M environs/
M environs/
M environs/
M environs/
M environs/open.go
M environs/
M provider/