Merge lp:~axwalk/juju-core/juju-failed-bootstrap-destroy-jenv into lp:~go-bot/juju-core/trunk

Proposed by Andrew Wilkins
Status: Merged
Approved by: Andrew Wilkins
Approved revision: no longer in the source branch.
Merged at revision: 2310
Proposed branch: lp:~axwalk/juju-core/juju-failed-bootstrap-destroy-jenv
Merge into: lp:~go-bot/juju-core/trunk
Diff against target: 213 lines (+69/-15)
6 files modified
cmd/juju/bootstrap.go (+18/-1)
cmd/juju/bootstrap_test.go (+22/-0)
cmd/juju/destroyenvironment_test.go (+4/-2)
cmd/juju/synctools.go (+9/-1)
environs/open.go (+7/-1)
provider/dummy/environs.go (+9/-10)
To merge this branch: bzr merge lp:~axwalk/juju-core/juju-failed-bootstrap-destroy-jenv
Reviewer Review Type Date Requested Status
Juju Engineering Pending
Review via email: mp+204421@code.launchpad.net

Commit message

cmd/juju: delete .jenv if bootstrap fails

If bootstrap fails, and the .jenv file did
not previously exist, then Juju will attempt
to destroy the environment in case of partial
creation. If this succeeds, the .jenv file
is destroyed. If it fails, an error message
is logged; we do not currently attempt to
forcefully remove the environment.

NOTE: if an environment is already prepared, say
by running sync-tools, then a failed bootstrap
will not attempt to destroy the environment.

Fixes lp:1247152

https://codereview.appspot.com/59560043/

Description of the change

cmd/juju: delete .jenv if bootstrap fails

If bootstrap fails, and the .jenv file did
not previously exist, then Juju will attempt
to destroy the environment in case of partial
creation. If this succeeds, the .jenv file
is destroyed. If it fails, an error message
is logged; we do not currently attempt to
forcefully remove the environment.

NOTE: if an environment is already prepared, say
by running sync-tools, then a failed bootstrap
will not attempt to destroy the environment.

Fixes lp:1247152

https://codereview.appspot.com/59560043/

To post a comment you must log in.
Revision history for this message
Andrew Wilkins (axwalk) wrote :

Reviewers: mp+204421_code.launchpad.net,

Message:
Please take a look.

Description:
cmd/juju: delete .jenv if bootstrap fails

If bootstrap fails, and the .jenv file did
not previously exist, then Juju will attempt
to destroy the environment in case of partial
creation. If this succeeds, the .jenv file
is destroyed. If environment destruction fails,
a warning message is displayed to the user
with directions to use destroy-environment with
a new --config-only flag.

juju destroy-environment now has a --config-only
flag that tells destroy-environment to only
destroy the configstore info for the named
environment. This is equivalent to deleting the
.jenv file, but allows for additional configstore
implementations in the future.

NOTE: if an environment is already prepared, say
by running sync-tools, then a failed bootstrap
will not attempt to destroy the environment.

Fixes lp:1247152

https://code.launchpad.net/~axwalk/juju-core/juju-failed-bootstrap-destroy-jenv/+merge/204421

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/59560043/

Affected files (+106, -26 lines):
   [revision details]
   cmd/juju/bootstrap.go
   cmd/juju/bootstrap_test.go
   cmd/juju/destroyenvironment.go
   cmd/juju/destroyenvironment_test.go
   environs/open.go
   provider/dummy/environs.go

Revision history for this message
Ian Booth (wallyworld) wrote :

I'm not sure about this. I think we're getting to the point where the
mental model is getting too complicated and exposes too much of Juju's
internals. Why not just go ahead and remove the jenv file automatically
under the circumstances described ie no previous jenv file, bootstrap
fails, destroy files, so remove jenv.
What's the use case for stand alone use of the --config-only option? If
the cloud artefacts are already gone, we could just inform the user that
their cloud env has disappeared and did they want to clean up the
remaining local config, and if they say yes, just do it.

https://codereview.appspot.com/59560043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2014/02/03 07:20:58, wallyworld wrote:
> I'm not sure about this. I think we're getting to the point where the
mental
> model is getting too complicated and exposes too much of Juju's
internals. Why
> not just go ahead and remove the jenv file automatically under the
circumstances
> described ie no previous jenv file, bootstrap fails, destroy files, so
remove
> jenv.

If we dispose of the .jenv file without attempting to destroy the
environment,
then the user cannot automatically release any partially created
environment.

> What's the use case for stand alone use of the --config-only option?
If the
> cloud artefacts are already gone, we could just inform the user that
their cloud
> env has disappeared and did they want to clean up the remaining local
config,
> and if they say yes, just do it.

The reason it exists is to clean up if bootstrap fails *and* the auto
destroy-environment fails. It would be nice if we could automatically
determine that this can be done and just do it, but I'm not convinced
it's practical.

How do you tell that the cloud artefacts are gone? By going to the
provider. What if you can't get to the provider because your config is
wrong? How can Juju tell the difference between a temporarily wrong
config and a permanently wrong one?

https://codereview.appspot.com/59560043/

Revision history for this message
Ian Booth (wallyworld) wrote :

On 2014/02/03 07:34:06, axw wrote:
> On 2014/02/03 07:20:58, wallyworld wrote:
> > I'm not sure about this. I think we're getting to the point where
the mental
> > model is getting too complicated and exposes too much of Juju's
internals. Why
> > not just go ahead and remove the jenv file automatically under the
> circumstances
> > described ie no previous jenv file, bootstrap fails, destroy files,
so remove
> > jenv.

> If we dispose of the .jenv file without attempting to destroy the
environment,
> then the user cannot automatically release any partially created
environment.

> > What's the use case for stand alone use of the --config-only option?
If the
> > cloud artefacts are already gone, we could just inform the user that
their
> cloud
> > env has disappeared and did they want to clean up the remaining
local config,
> > and if they say yes, just do it.

> The reason it exists is to clean up if bootstrap fails *and* the auto
> destroy-environment fails. It would be nice if we could automatically
determine
> that this can be done and just do it, but I'm not convinced it's
practical.

> How do you tell that the cloud artefacts are gone? By going to the
provider.
> What if you can't get to the provider because your config is wrong?
How can Juju
> tell the difference between a temporarily wrong config and a
permanently wrong
> one?

I think there's 2 main use cases?
1. Bootstrap of new env fails
In this case, we know the that jenv file did not previously exist so if
auto-destroy fails, we can (probably after asking) offer to the user
that any remaining juju configuration files be cleaned up and so leave
their system as it was prior to the initial bootstrap.
2. Someone deletes cloud artefacts outside of juju
So now the user may see that juju commands fail and they want to clean
up. So they juju destroy-env. It fails cause stuff has already been
deleted. So like in the first case above, we can just prompt them that
their config will be deleted. Here the wording of the prompt will be
slightly different but the end result is the same.

I guess there's a danger that if there's some transient failure reaching
the cloud we could remove the jenv and leave the user with cloud
resources still active. We could incorporate this into the above prompts
eg "Juju cannot contact the cloud to delete any running instances, this
may be a temporary failure, did you want to try again or just nuke your
env config". If they elect to clean up the config, we can let them know
that they should ensure that nothing is left running, Juju tried but
failed so now it's up to them.

I guess my main wish is that the user be guided as to what to do and the
tool does it at a high enough level that they don't have to try and
understand the internal workings of Juju, as opposed to exiting and
making them run a command with yet another option. And like
destroy-environment -y, the user could allow juju to do what it thinks
needs to be done without prompting.

https://codereview.appspot.com/59560043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :
Download full text (3.4 KiB)

On 2014/02/03 08:03:18, wallyworld wrote:
> On 2014/02/03 07:34:06, axw wrote:
> > On 2014/02/03 07:20:58, wallyworld wrote:
> > > I'm not sure about this. I think we're getting to the point where
the mental
> > > model is getting too complicated and exposes too much of Juju's
internals.
> Why
> > > not just go ahead and remove the jenv file automatically under the
> > circumstances
> > > described ie no previous jenv file, bootstrap fails, destroy
files, so
> remove
> > > jenv.
> >
> > If we dispose of the .jenv file without attempting to destroy the
environment,
> > then the user cannot automatically release any partially created
environment.
> >
> > > What's the use case for stand alone use of the --config-only
option? If the
> > > cloud artefacts are already gone, we could just inform the user
that their
> > cloud
> > > env has disappeared and did they want to clean up the remaining
local
> config,
> > > and if they say yes, just do it.
> >
> > The reason it exists is to clean up if bootstrap fails *and* the
auto
> > destroy-environment fails. It would be nice if we could
automatically
> determine
> > that this can be done and just do it, but I'm not convinced it's
practical.
> >
> > How do you tell that the cloud artefacts are gone? By going to the
provider.
> > What if you can't get to the provider because your config is wrong?
How can
> Juju
> > tell the difference between a temporarily wrong config and a
permanently wrong
> > one?

> I think there's 2 main use cases?
> 1. Bootstrap of new env fails
> In this case, we know the that jenv file did not previously exist so
if
> auto-destroy fails, we can (probably after asking) offer to the user
that any
> remaining juju configuration files be cleaned up and so leave their
system as it
> was prior to the initial bootstrap.
> 2. Someone deletes cloud artefacts outside of juju
> So now the user may see that juju commands fail and they want to clean
up. So
> they juju destroy-env. It fails cause stuff has already been deleted.
So like in
> the first case above, we can just prompt them that their config will
be deleted.
> Here the wording of the prompt will be slightly different but the end
result is
> the same.

> I guess there's a danger that if there's some transient failure
reaching the
> cloud we could remove the jenv and leave the user with cloud resources
still
> active. We could incorporate this into the above prompts eg "Juju
cannot contact
> the cloud to delete any running instances, this may be a temporary
failure, did
> you want to try again or just nuke your env config". If they elect to
clean up
> the config, we can let them know that they should ensure that nothing
is left
> running, Juju tried but failed so now it's up to them.

> I guess my main wish is that the user be guided as to what to do and
the tool
> does it at a high enough level that they don't have to try and
understand the
> internal workings of Juju, as opposed to exiting and making them run a
command
> with yet another option. And like destroy-environment -y, the user
could allow
> juju to do what it thinks needs to be done without prompting.

Fair call about it being too low-level. I'm just not keen on adding
pr...

Read more...

Revision history for this message
Roger Peppe (rogpeppe) wrote :

Thanks a lot for doing this - it will be much appreciated.

We should also destroy the environment if sync-tools fails and the env
didn't previously exist, I
think.

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go
File cmd/juju/bootstrap.go (right):

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go#newcode118
cmd/juju/bootstrap.go:118:
I'd prefer it if result had "err" in its name here,
either by actually naming it "err", or perhaps by
naming it "resultErr".

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go#newcode135
cmd/juju/bootstrap.go:135:
I think this case is unusual enough that we don't actually need to
special "config-only" flag. The error message will probably contain the
file name anyway. I think a better approach would be for
destroy-environment to delete the config file if it gets a definitive
"not found" error when trying to delete the environment.

https://codereview.appspot.com/59560043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

Please take a look.

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go
File cmd/juju/bootstrap.go (right):

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go#newcode118
cmd/juju/bootstrap.go:118:
On 2014/02/03 08:32:22, rog wrote:
> I'd prefer it if result had "err" in its name here,
> either by actually naming it "err", or perhaps by
> naming it "resultErr".

Done.

https://codereview.appspot.com/59560043/diff/1/cmd/juju/bootstrap.go#newcode135
cmd/juju/bootstrap.go:135:
On 2014/02/03 08:32:22, rog wrote:
> I think this case is unusual enough that we don't actually need to
special
> "config-only" flag. The error message will probably contain the file
name
> anyway. I think a better approach would be for destroy-environment to
delete the
> config file if it gets a definitive "not found" error when trying to
delete the
> environment.

Okay. I'm going to take the --config-only bit out of the CL for now.
That can be addressed in a followup.

https://codereview.appspot.com/59560043/

Revision history for this message
Andrew Wilkins (axwalk) wrote :

On 2014/02/03 08:32:21, rog wrote:
> Thanks a lot for doing this - it will be much appreciated.

> We should also destroy the environment if sync-tools fails and the env
didn't
> previously exist, I
> think.

Done.

https://codereview.appspot.com/59560043/

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cmd/juju/bootstrap.go'
2--- cmd/juju/bootstrap.go 2014-01-31 00:14:11 +0000
3+++ cmd/juju/bootstrap.go 2014-02-04 02:09:35 +0000
4@@ -21,6 +21,7 @@
5 "launchpad.net/juju-core/environs/imagemetadata"
6 "launchpad.net/juju-core/environs/sync"
7 "launchpad.net/juju-core/environs/tools"
8+ "launchpad.net/juju-core/errors"
9 "launchpad.net/juju-core/provider"
10 "launchpad.net/juju-core/utils/set"
11 "launchpad.net/juju-core/version"
12@@ -111,18 +112,34 @@
13 return c.Context.Stderr
14 }
15
16+func destroyPreparedEnviron(env environs.Environ, store configstore.Storage, err *error, action string) {
17+ if *err == nil {
18+ return
19+ }
20+ if err := environs.Destroy(env, store); err != nil {
21+ logger.Errorf("%s failed, and the environment could not be destroyed: %v", action, err)
22+ }
23+}
24+
25 // Run connects to the environment specified on the command line and bootstraps
26 // a juju in that environment if none already exists. If there is as yet no environments.yaml file,
27 // the user is informed how to create one.
28-func (c *BootstrapCommand) Run(ctx *cmd.Context) error {
29+func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
30 store, err := configstore.Default()
31 if err != nil {
32 return err
33 }
34+ var existing bool
35+ if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) {
36+ existing = true
37+ }
38 environ, err := environs.PrepareFromName(c.EnvName, store)
39 if err != nil {
40 return err
41 }
42+ if !existing {
43+ defer destroyPreparedEnviron(environ, store, &resultErr, "Bootstrap")
44+ }
45 bootstrapContext := bootstrapContext{ctx}
46 // If the environment has a special bootstrap Storage, use it wherever
47 // we'd otherwise use environ.Storage.
48
49=== modified file 'cmd/juju/bootstrap_test.go'
50--- cmd/juju/bootstrap_test.go 2014-01-30 06:21:03 +0000
51+++ cmd/juju/bootstrap_test.go 2014-02-04 02:09:35 +0000
52@@ -476,6 +476,28 @@
53 c.Assert(errText, gc.Matches, expectedErrText)
54 }
55
56+func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) {
57+ _, fake := makeEmptyFakeHome(c)
58+ defer fake.Restore()
59+ opc, errc := runCommand(nullContext(), new(BootstrapCommand), "-e", "brokenenv")
60+ err := <-errc
61+ c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken")
62+ var opDestroy *dummy.OpDestroy
63+ for opDestroy == nil {
64+ select {
65+ case op := <-opc:
66+ switch op := op.(type) {
67+ case dummy.OpDestroy:
68+ opDestroy = &op
69+ }
70+ default:
71+ c.Error("expected call to env.Destroy")
72+ return
73+ }
74+ }
75+ c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken")
76+}
77+
78 // createToolsSource writes the mock tools and metadata into a temporary
79 // directory and returns it.
80 func createToolsSource(c *gc.C, versions []version.Binary) string {
81
82=== modified file 'cmd/juju/destroyenvironment_test.go'
83--- cmd/juju/destroyenvironment_test.go 2014-01-28 11:18:42 +0000
84+++ cmd/juju/destroyenvironment_test.go 2014-02-04 02:09:35 +0000
85@@ -94,8 +94,10 @@
86
87 // destroy with broken environment
88 opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand), "dummyenv", "--yes")
89- c.Check(<-opc, gc.IsNil)
90- c.Check(<-errc, gc.ErrorMatches, "dummy.Destroy is broken")
91+ op, ok := (<-opc).(dummy.OpDestroy)
92+ c.Assert(ok, jc.IsTrue)
93+ c.Assert(op.Error, gc.ErrorMatches, "dummy.Destroy is broken")
94+ c.Check(<-errc, gc.Equals, op.Error)
95 c.Check(<-opc, gc.IsNil)
96 }
97
98
99=== modified file 'cmd/juju/synctools.go'
100--- cmd/juju/synctools.go 2014-01-28 04:58:43 +0000
101+++ cmd/juju/synctools.go 2014-02-04 02:09:35 +0000
102@@ -12,6 +12,7 @@
103 "launchpad.net/juju-core/environs/configstore"
104 "launchpad.net/juju-core/environs/filestorage"
105 "launchpad.net/juju-core/environs/sync"
106+ "launchpad.net/juju-core/errors"
107 "launchpad.net/juju-core/version"
108 )
109
110@@ -72,7 +73,7 @@
111 return cmd.CheckEmpty(args)
112 }
113
114-func (c *SyncToolsCommand) Run(ctx *cmd.Context) error {
115+func (c *SyncToolsCommand) Run(ctx *cmd.Context) (resultErr error) {
116 // Register writer for output on screen.
117 loggo.RegisterWriter("synctools", cmd.NewCommandLogWriter("juju.environs.sync", ctx.Stdout, ctx.Stderr), loggo.INFO)
118 defer loggo.RemoveWriter("synctools")
119@@ -80,10 +81,17 @@
120 if err != nil {
121 return err
122 }
123+ var existing bool
124+ if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) {
125+ existing = true
126+ }
127 environ, err := environs.PrepareFromName(c.EnvName, store)
128 if err != nil {
129 return err
130 }
131+ if !existing {
132+ defer destroyPreparedEnviron(environ, store, &resultErr, "Sync-tools")
133+ }
134
135 target := environ.Storage()
136 if c.localDir != "" {
137
138=== modified file 'environs/open.go'
139--- environs/open.go 2013-10-04 12:18:17 +0000
140+++ environs/open.go 2014-02-04 02:09:35 +0000
141@@ -235,7 +235,13 @@
142 if err := env.Destroy(); err != nil {
143 return err
144 }
145- info, err := store.ReadInfo(name)
146+ return DestroyInfo(name, store)
147+}
148+
149+// DestroyInfo destroys the configuration data for the named
150+// environment from the given store.
151+func DestroyInfo(envName string, store configstore.Storage) error {
152+ info, err := store.ReadInfo(envName)
153 if err != nil {
154 if errors.IsNotFoundError(err) {
155 return nil
156
157=== modified file 'provider/dummy/environs.go'
158--- provider/dummy/environs.go 2014-01-28 04:58:43 +0000
159+++ provider/dummy/environs.go 2014-02-04 02:09:35 +0000
160@@ -97,17 +97,16 @@
161 // Operation represents an action on the dummy provider.
162 type Operation interface{}
163
164-type GenericOperation struct {
165- Env string
166-}
167-
168 type OpBootstrap struct {
169 Context environs.BootstrapContext
170 Env string
171 Constraints constraints.Value
172 }
173
174-type OpDestroy GenericOperation
175+type OpDestroy struct {
176+ Env string
177+ Error error
178+}
179
180 type OpStartInstance struct {
181 Env string
182@@ -637,11 +636,8 @@
183 return nil
184 }
185
186-func (e *environ) Destroy() error {
187+func (e *environ) Destroy() (res error) {
188 defer delay()
189- if err := e.checkBroken("Destroy"); err != nil {
190- return err
191- }
192 estate, err := e.state()
193 if err != nil {
194 if err == provider.ErrDestroyed {
195@@ -649,6 +645,10 @@
196 }
197 return err
198 }
199+ defer func() { estate.ops <- OpDestroy{Env: estate.name, Error: res} }()
200+ if err := e.checkBroken("Destroy"); err != nil {
201+ return err
202+ }
203 p := &providerInstance
204 p.mu.Lock()
205 delete(p.state, estate.id)
206@@ -656,7 +656,6 @@
207
208 estate.mu.Lock()
209 defer estate.mu.Unlock()
210- estate.ops <- OpDestroy{Env: estate.name}
211 estate.destroy()
212 return nil
213 }

Subscribers

People subscribed via source and target branches

to status/vote changes: