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
=== modified file 'cmd/juju/bootstrap.go'
--- cmd/juju/bootstrap.go 2014-01-31 00:14:11 +0000
+++ cmd/juju/bootstrap.go 2014-02-04 02:09:35 +0000
@@ -21,6 +21,7 @@
21 "launchpad.net/juju-core/environs/imagemetadata"21 "launchpad.net/juju-core/environs/imagemetadata"
22 "launchpad.net/juju-core/environs/sync"22 "launchpad.net/juju-core/environs/sync"
23 "launchpad.net/juju-core/environs/tools"23 "launchpad.net/juju-core/environs/tools"
24 "launchpad.net/juju-core/errors"
24 "launchpad.net/juju-core/provider"25 "launchpad.net/juju-core/provider"
25 "launchpad.net/juju-core/utils/set"26 "launchpad.net/juju-core/utils/set"
26 "launchpad.net/juju-core/version"27 "launchpad.net/juju-core/version"
@@ -111,18 +112,34 @@
111 return c.Context.Stderr112 return c.Context.Stderr
112}113}
113114
115func destroyPreparedEnviron(env environs.Environ, store configstore.Storage, err *error, action string) {
116 if *err == nil {
117 return
118 }
119 if err := environs.Destroy(env, store); err != nil {
120 logger.Errorf("%s failed, and the environment could not be destroyed: %v", action, err)
121 }
122}
123
114// Run connects to the environment specified on the command line and bootstraps124// Run connects to the environment specified on the command line and bootstraps
115// a juju in that environment if none already exists. If there is as yet no environments.yaml file,125// a juju in that environment if none already exists. If there is as yet no environments.yaml file,
116// the user is informed how to create one.126// the user is informed how to create one.
117func (c *BootstrapCommand) Run(ctx *cmd.Context) error {127func (c *BootstrapCommand) Run(ctx *cmd.Context) (resultErr error) {
118 store, err := configstore.Default()128 store, err := configstore.Default()
119 if err != nil {129 if err != nil {
120 return err130 return err
121 }131 }
132 var existing bool
133 if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) {
134 existing = true
135 }
122 environ, err := environs.PrepareFromName(c.EnvName, store)136 environ, err := environs.PrepareFromName(c.EnvName, store)
123 if err != nil {137 if err != nil {
124 return err138 return err
125 }139 }
140 if !existing {
141 defer destroyPreparedEnviron(environ, store, &resultErr, "Bootstrap")
142 }
126 bootstrapContext := bootstrapContext{ctx}143 bootstrapContext := bootstrapContext{ctx}
127 // If the environment has a special bootstrap Storage, use it wherever144 // If the environment has a special bootstrap Storage, use it wherever
128 // we'd otherwise use environ.Storage.145 // we'd otherwise use environ.Storage.
129146
=== modified file 'cmd/juju/bootstrap_test.go'
--- cmd/juju/bootstrap_test.go 2014-01-30 06:21:03 +0000
+++ cmd/juju/bootstrap_test.go 2014-02-04 02:09:35 +0000
@@ -476,6 +476,28 @@
476 c.Assert(errText, gc.Matches, expectedErrText)476 c.Assert(errText, gc.Matches, expectedErrText)
477}477}
478478
479func (s *BootstrapSuite) TestBootstrapDestroy(c *gc.C) {
480 _, fake := makeEmptyFakeHome(c)
481 defer fake.Restore()
482 opc, errc := runCommand(nullContext(), new(BootstrapCommand), "-e", "brokenenv")
483 err := <-errc
484 c.Assert(err, gc.ErrorMatches, "dummy.Bootstrap is broken")
485 var opDestroy *dummy.OpDestroy
486 for opDestroy == nil {
487 select {
488 case op := <-opc:
489 switch op := op.(type) {
490 case dummy.OpDestroy:
491 opDestroy = &op
492 }
493 default:
494 c.Error("expected call to env.Destroy")
495 return
496 }
497 }
498 c.Assert(opDestroy.Error, gc.ErrorMatches, "dummy.Destroy is broken")
499}
500
479// createToolsSource writes the mock tools and metadata into a temporary501// createToolsSource writes the mock tools and metadata into a temporary
480// directory and returns it.502// directory and returns it.
481func createToolsSource(c *gc.C, versions []version.Binary) string {503func createToolsSource(c *gc.C, versions []version.Binary) string {
482504
=== modified file 'cmd/juju/destroyenvironment_test.go'
--- cmd/juju/destroyenvironment_test.go 2014-01-28 11:18:42 +0000
+++ cmd/juju/destroyenvironment_test.go 2014-02-04 02:09:35 +0000
@@ -94,8 +94,10 @@
9494
95 // destroy with broken environment95 // destroy with broken environment
96 opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand), "dummyenv", "--yes")96 opc, errc := runCommand(nullContext(), new(DestroyEnvironmentCommand), "dummyenv", "--yes")
97 c.Check(<-opc, gc.IsNil)97 op, ok := (<-opc).(dummy.OpDestroy)
98 c.Check(<-errc, gc.ErrorMatches, "dummy.Destroy is broken")98 c.Assert(ok, jc.IsTrue)
99 c.Assert(op.Error, gc.ErrorMatches, "dummy.Destroy is broken")
100 c.Check(<-errc, gc.Equals, op.Error)
99 c.Check(<-opc, gc.IsNil)101 c.Check(<-opc, gc.IsNil)
100}102}
101103
102104
=== modified file 'cmd/juju/synctools.go'
--- cmd/juju/synctools.go 2014-01-28 04:58:43 +0000
+++ cmd/juju/synctools.go 2014-02-04 02:09:35 +0000
@@ -12,6 +12,7 @@
12 "launchpad.net/juju-core/environs/configstore"12 "launchpad.net/juju-core/environs/configstore"
13 "launchpad.net/juju-core/environs/filestorage"13 "launchpad.net/juju-core/environs/filestorage"
14 "launchpad.net/juju-core/environs/sync"14 "launchpad.net/juju-core/environs/sync"
15 "launchpad.net/juju-core/errors"
15 "launchpad.net/juju-core/version"16 "launchpad.net/juju-core/version"
16)17)
1718
@@ -72,7 +73,7 @@
72 return cmd.CheckEmpty(args)73 return cmd.CheckEmpty(args)
73}74}
7475
75func (c *SyncToolsCommand) Run(ctx *cmd.Context) error {76func (c *SyncToolsCommand) Run(ctx *cmd.Context) (resultErr error) {
76 // Register writer for output on screen.77 // Register writer for output on screen.
77 loggo.RegisterWriter("synctools", cmd.NewCommandLogWriter("juju.environs.sync", ctx.Stdout, ctx.Stderr), loggo.INFO)78 loggo.RegisterWriter("synctools", cmd.NewCommandLogWriter("juju.environs.sync", ctx.Stdout, ctx.Stderr), loggo.INFO)
78 defer loggo.RemoveWriter("synctools")79 defer loggo.RemoveWriter("synctools")
@@ -80,10 +81,17 @@
80 if err != nil {81 if err != nil {
81 return err82 return err
82 }83 }
84 var existing bool
85 if _, err := store.ReadInfo(c.EnvName); !errors.IsNotFoundError(err) {
86 existing = true
87 }
83 environ, err := environs.PrepareFromName(c.EnvName, store)88 environ, err := environs.PrepareFromName(c.EnvName, store)
84 if err != nil {89 if err != nil {
85 return err90 return err
86 }91 }
92 if !existing {
93 defer destroyPreparedEnviron(environ, store, &resultErr, "Sync-tools")
94 }
8795
88 target := environ.Storage()96 target := environ.Storage()
89 if c.localDir != "" {97 if c.localDir != "" {
9098
=== modified file 'environs/open.go'
--- environs/open.go 2013-10-04 12:18:17 +0000
+++ environs/open.go 2014-02-04 02:09:35 +0000
@@ -235,7 +235,13 @@
235 if err := env.Destroy(); err != nil {235 if err := env.Destroy(); err != nil {
236 return err236 return err
237 }237 }
238 info, err := store.ReadInfo(name)238 return DestroyInfo(name, store)
239}
240
241// DestroyInfo destroys the configuration data for the named
242// environment from the given store.
243func DestroyInfo(envName string, store configstore.Storage) error {
244 info, err := store.ReadInfo(envName)
239 if err != nil {245 if err != nil {
240 if errors.IsNotFoundError(err) {246 if errors.IsNotFoundError(err) {
241 return nil247 return nil
242248
=== modified file 'provider/dummy/environs.go'
--- provider/dummy/environs.go 2014-01-28 04:58:43 +0000
+++ provider/dummy/environs.go 2014-02-04 02:09:35 +0000
@@ -97,17 +97,16 @@
97// Operation represents an action on the dummy provider.97// Operation represents an action on the dummy provider.
98type Operation interface{}98type Operation interface{}
9999
100type GenericOperation struct {
101 Env string
102}
103
104type OpBootstrap struct {100type OpBootstrap struct {
105 Context environs.BootstrapContext101 Context environs.BootstrapContext
106 Env string102 Env string
107 Constraints constraints.Value103 Constraints constraints.Value
108}104}
109105
110type OpDestroy GenericOperation106type OpDestroy struct {
107 Env string
108 Error error
109}
111110
112type OpStartInstance struct {111type OpStartInstance struct {
113 Env string112 Env string
@@ -637,11 +636,8 @@
637 return nil636 return nil
638}637}
639638
640func (e *environ) Destroy() error {639func (e *environ) Destroy() (res error) {
641 defer delay()640 defer delay()
642 if err := e.checkBroken("Destroy"); err != nil {
643 return err
644 }
645 estate, err := e.state()641 estate, err := e.state()
646 if err != nil {642 if err != nil {
647 if err == provider.ErrDestroyed {643 if err == provider.ErrDestroyed {
@@ -649,6 +645,10 @@
649 }645 }
650 return err646 return err
651 }647 }
648 defer func() { estate.ops <- OpDestroy{Env: estate.name, Error: res} }()
649 if err := e.checkBroken("Destroy"); err != nil {
650 return err
651 }
652 p := &providerInstance652 p := &providerInstance
653 p.mu.Lock()653 p.mu.Lock()
654 delete(p.state, estate.id)654 delete(p.state, estate.id)
@@ -656,7 +656,6 @@
656656
657 estate.mu.Lock()657 estate.mu.Lock()
658 defer estate.mu.Unlock()658 defer estate.mu.Unlock()
659 estate.ops <- OpDestroy{Env: estate.name}
660 estate.destroy()659 estate.destroy()
661 return nil660 return nil
662}661}

Subscribers

People subscribed via source and target branches

to status/vote changes: