github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/cmd/juju/controller/kill.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package controller
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/gnuflag"
    13  	"github.com/juju/utils/clock"
    14  
    15  	"github.com/juju/juju/apiserver/common"
    16  	"github.com/juju/juju/cmd/modelcmd"
    17  	"github.com/juju/juju/environs"
    18  	"github.com/juju/juju/environs/config"
    19  )
    20  
    21  const killDoc = `
    22  Forcibly destroy the specified controller.  If the API server is accessible,
    23  this command will attempt to destroy the controller model and all hosted models
    24  and their resources.
    25  
    26  If the API server is unreachable, the machines of the controller model will be
    27  destroyed through the cloud provisioner.  If there are additional machines,
    28  including machines within hosted models, these machines will not be destroyed
    29  and will never be reconnected to the Juju controller being destroyed.
    30  
    31  The normal process of killing the controller will involve watching the hosted
    32  models as they are brought down in a controlled manner. If for some reason the
    33  models do not stop cleanly, there is a default five minute timeout. If no change
    34  in the model state occurs for the duration of this timeout, the command will
    35  stop watching and destroy the models directly through the cloud provider.
    36  
    37  See also:
    38      destroy-controller
    39      unregister
    40  `
    41  
    42  // NewKillCommand returns a command to kill a controller. Killing is a forceful
    43  // destroy.
    44  func NewKillCommand() cmd.Command {
    45  	// Even though this command is all about killing a controller we end up
    46  	// needing environment endpoints so we can fall back to the client destroy
    47  	// environment method. This shouldn't really matter in practice as the
    48  	// user trying to take down the controller will need to have access to the
    49  	// controller environment anyway.
    50  	return wrapKillCommand(&killCommand{
    51  		clock: clock.WallClock,
    52  	}, nil, clock.WallClock)
    53  }
    54  
    55  // wrapKillCommand provides the common wrapping used by tests and
    56  // the default NewKillCommand above.
    57  func wrapKillCommand(kill *killCommand, apiOpen modelcmd.APIOpener, clock clock.Clock) cmd.Command {
    58  	if apiOpen == nil {
    59  		apiOpen = modelcmd.OpenFunc(kill.JujuCommandBase.NewAPIRoot)
    60  	}
    61  	openStrategy := modelcmd.NewTimeoutOpener(apiOpen, clock, 10*time.Second)
    62  	return modelcmd.WrapController(
    63  		kill,
    64  		modelcmd.WrapControllerSkipControllerFlags,
    65  		modelcmd.WrapControllerSkipDefaultController,
    66  		modelcmd.WrapControllerAPIOpener(openStrategy),
    67  	)
    68  }
    69  
    70  // killCommand kills the specified controller.
    71  type killCommand struct {
    72  	destroyCommandBase
    73  
    74  	clock   clock.Clock
    75  	timeout time.Duration
    76  }
    77  
    78  // SetFlags implements Command.SetFlags.
    79  func (c *killCommand) SetFlags(f *gnuflag.FlagSet) {
    80  	c.destroyCommandBase.SetFlags(f)
    81  	f.Var(newDurationValue(time.Minute*5, &c.timeout), "t", "Timeout before direct destruction")
    82  	f.Var(newDurationValue(time.Minute*5, &c.timeout), "timeout", "")
    83  }
    84  
    85  // Info implements Command.Info.
    86  func (c *killCommand) Info() *cmd.Info {
    87  	return &cmd.Info{
    88  		Name:    "kill-controller",
    89  		Args:    "<controller name>",
    90  		Purpose: "Forcibly terminate all machines and other associated resources for a Juju controller.",
    91  		Doc:     killDoc,
    92  	}
    93  }
    94  
    95  // Init implements Command.Init.
    96  func (c *killCommand) Init(args []string) error {
    97  	return c.destroyCommandBase.Init(args)
    98  }
    99  
   100  // Run implements Command.Run
   101  func (c *killCommand) Run(ctx *cmd.Context) error {
   102  	controllerName := c.ControllerName()
   103  	store := c.ClientStore()
   104  	if !c.assumeYes {
   105  		if err := confirmDestruction(ctx, controllerName); err != nil {
   106  			return err
   107  		}
   108  	}
   109  
   110  	// Attempt to connect to the API.
   111  	api, err := c.getControllerAPI()
   112  	switch {
   113  	case err == nil:
   114  		defer api.Close()
   115  	case errors.Cause(err) == common.ErrPerm:
   116  		return errors.Annotate(err, "cannot destroy controller")
   117  	default:
   118  		if errors.Cause(err) != modelcmd.ErrConnTimedOut {
   119  			logger.Debugf("unable to open api: %s", err)
   120  		}
   121  		ctx.Infof("Unable to open API: %s\n", err)
   122  		api = nil
   123  	}
   124  
   125  	// Obtain controller environ so we can clean up afterwards.
   126  	controllerEnviron, err := c.getControllerEnviron(ctx, store, controllerName, api)
   127  	if err != nil {
   128  		return errors.Annotate(err, "getting controller environ")
   129  	}
   130  	// If we were unable to connect to the API, just destroy the controller through
   131  	// the environs interface.
   132  	if api == nil {
   133  		ctx.Infof("Unable to connect to the API server, destroying through provider")
   134  		return environs.Destroy(controllerName, controllerEnviron, store)
   135  	}
   136  
   137  	// Attempt to destroy the controller and all environments.
   138  	err = api.DestroyController(true)
   139  	if err != nil {
   140  		ctx.Infof("Unable to destroy controller through the API: %s\nDestroying through provider", err)
   141  		return environs.Destroy(controllerName, controllerEnviron, store)
   142  	}
   143  
   144  	ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName)
   145  
   146  	uuid := controllerEnviron.Config().UUID()
   147  	if err := c.WaitForModels(ctx, api, uuid); err != nil {
   148  		c.DirectDestroyRemaining(ctx, api)
   149  	}
   150  	return environs.Destroy(controllerName, controllerEnviron, store)
   151  }
   152  
   153  // DirectDestroyRemaining will attempt to directly destroy any remaining
   154  // models that have machines left.
   155  func (c *killCommand) DirectDestroyRemaining(ctx *cmd.Context, api destroyControllerAPI) {
   156  	hasErrors := false
   157  	hostedConfig, err := api.HostedModelConfigs()
   158  	if err != nil {
   159  		hasErrors = true
   160  		logger.Errorf("unable to retrieve hosted model config: %v", err)
   161  	}
   162  	for _, model := range hostedConfig {
   163  		ctx.Infof("Killing %s/%s directly", model.Owner.Canonical(), model.Name)
   164  		cfg, err := config.New(config.NoDefaults, model.Config)
   165  		if err != nil {
   166  			logger.Errorf(err.Error())
   167  			hasErrors = true
   168  			continue
   169  		}
   170  		env, err := environs.New(environs.OpenParams{
   171  			Cloud:  model.CloudSpec,
   172  			Config: cfg,
   173  		})
   174  		if err != nil {
   175  			logger.Errorf(err.Error())
   176  			hasErrors = true
   177  			continue
   178  		}
   179  		if err := env.Destroy(); err != nil {
   180  			logger.Errorf(err.Error())
   181  			hasErrors = true
   182  		} else {
   183  			ctx.Infof("  done")
   184  		}
   185  	}
   186  	if hasErrors {
   187  		logger.Errorf("there were problems destroying some models, manual intervention may be necessary to ensure resources are released")
   188  	} else {
   189  		ctx.Infof("All hosted models destroyed, cleaning up controller machines")
   190  	}
   191  }
   192  
   193  // WaitForModels will wait for the models to bring themselves down nicely.
   194  // It will return the UUIDs of any models that need to be removed forceably.
   195  func (c *killCommand) WaitForModels(ctx *cmd.Context, api destroyControllerAPI, uuid string) error {
   196  	thirtySeconds := (time.Second * 30)
   197  	updateStatus := newTimedStatusUpdater(ctx, api, uuid, c.clock)
   198  
   199  	ctrStatus, modelsStatus := updateStatus(0)
   200  	lastStatus := ctrStatus
   201  	lastChange := c.clock.Now().Truncate(time.Second)
   202  	deadline := lastChange.Add(c.timeout)
   203  	for ; hasUnDeadModels(modelsStatus) && (deadline.After(c.clock.Now())); ctrStatus, modelsStatus = updateStatus(5 * time.Second) {
   204  		now := c.clock.Now().Truncate(time.Second)
   205  		if ctrStatus != lastStatus {
   206  			lastStatus = ctrStatus
   207  			lastChange = now
   208  			deadline = lastChange.Add(c.timeout)
   209  		}
   210  		timeSinceLastChange := now.Sub(lastChange)
   211  		timeUntilDestruction := deadline.Sub(now)
   212  		warning := ""
   213  		// We want to show the warning if it has been more than 30 seconds since
   214  		// the last change, or we are within 30 seconds of our timeout.
   215  		if timeSinceLastChange > thirtySeconds || timeUntilDestruction < thirtySeconds {
   216  			warning = fmt.Sprintf(", will kill machines directly in %s", timeUntilDestruction)
   217  		}
   218  		ctx.Infof("%s%s", fmtCtrStatus(ctrStatus), warning)
   219  		for _, modelStatus := range modelsStatus {
   220  			ctx.Verbosef(fmtModelStatus(modelStatus))
   221  		}
   222  	}
   223  	if hasUnDeadModels(modelsStatus) {
   224  		return errors.New("timed out")
   225  	} else {
   226  		ctx.Infof("All hosted models reclaimed, cleaning up controller machines")
   227  	}
   228  	return nil
   229  }
   230  
   231  type durationValue time.Duration
   232  
   233  func newDurationValue(value time.Duration, p *time.Duration) *durationValue {
   234  	*p = value
   235  	return (*durationValue)(p)
   236  }
   237  
   238  func (d *durationValue) Set(s string) error {
   239  	v, err := time.ParseDuration(s)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	*d = durationValue(v)
   244  	return err
   245  }
   246  
   247  func (d *durationValue) String() string { return (*time.Duration)(d).String() }