github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"time"
     8  
     9  	"github.com/juju/cmd"
    10  	"github.com/juju/errors"
    11  	"github.com/juju/utils/clock"
    12  	"launchpad.net/gnuflag"
    13  
    14  	"github.com/juju/juju/apiserver/common"
    15  	"github.com/juju/juju/cmd/modelcmd"
    16  	"github.com/juju/juju/environs"
    17  )
    18  
    19  const killDoc = `
    20  Forcibly destroy the specified controller.  If the API server is accessible,
    21  this command will attempt to destroy the controller model and all
    22  hosted models and their resources.
    23  
    24  If the API server is unreachable, the machines of the controller model
    25  will be destroyed through the cloud provisioner.  If there are additional
    26  machines, including machines within hosted models, these machines will
    27  not be destroyed and will never be reconnected to the Juju controller being
    28  destroyed. 
    29  `
    30  
    31  // NewKillCommand returns a command to kill a controller. Killing is a forceful
    32  // destroy.
    33  func NewKillCommand() cmd.Command {
    34  	// Even though this command is all about killing a controller we end up
    35  	// needing environment endpoints so we can fall back to the client destroy
    36  	// environment method. This shouldn't really matter in practice as the
    37  	// user trying to take down the controller will need to have access to the
    38  	// controller environment anyway.
    39  	return wrapKillCommand(&killCommand{}, nil, clock.WallClock)
    40  }
    41  
    42  // wrapKillCommand provides the common wrapping used by tests and
    43  // the default NewKillCommand above.
    44  func wrapKillCommand(kill *killCommand, apiOpen modelcmd.APIOpener, clock clock.Clock) cmd.Command {
    45  	if apiOpen == nil {
    46  		apiOpen = modelcmd.OpenFunc(kill.JujuCommandBase.NewAPIRoot)
    47  	}
    48  	openStrategy := modelcmd.NewTimeoutOpener(apiOpen, clock, 10*time.Second)
    49  	return modelcmd.WrapController(
    50  		kill,
    51  		modelcmd.ControllerSkipFlags,
    52  		modelcmd.ControllerSkipDefault,
    53  		modelcmd.ControllerAPIOpener(openStrategy),
    54  	)
    55  }
    56  
    57  // killCommand kills the specified controller.
    58  type killCommand struct {
    59  	destroyCommandBase
    60  }
    61  
    62  // Info implements Command.Info.
    63  func (c *killCommand) Info() *cmd.Info {
    64  	return &cmd.Info{
    65  		Name:    "kill-controller",
    66  		Args:    "<controller name>",
    67  		Purpose: "forcibly terminate all machines and other associated resources for a juju controller",
    68  		Doc:     killDoc,
    69  	}
    70  }
    71  
    72  // SetFlags implements Command.SetFlags.
    73  func (c *killCommand) SetFlags(f *gnuflag.FlagSet) {
    74  	f.BoolVar(&c.assumeYes, "y", false, "do not ask for confirmation")
    75  	f.BoolVar(&c.assumeYes, "yes", false, "")
    76  }
    77  
    78  // Init implements Command.Init.
    79  func (c *killCommand) Init(args []string) error {
    80  	return c.destroyCommandBase.Init(args)
    81  }
    82  
    83  // Run implements Command.Run
    84  func (c *killCommand) Run(ctx *cmd.Context) error {
    85  	controllerName := c.ControllerName()
    86  	store := c.ClientStore()
    87  	controllerDetails, err := store.ControllerByName(controllerName)
    88  	if err != nil {
    89  		return errors.Annotate(err, "cannot read controller info")
    90  	}
    91  
    92  	if !c.assumeYes {
    93  		if err = confirmDestruction(ctx, controllerName); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	// Attempt to connect to the API.
    99  	api, err := c.getControllerAPI()
   100  	switch {
   101  	case err == nil:
   102  		defer api.Close()
   103  	case errors.Cause(err) == common.ErrPerm:
   104  		return errors.Annotate(err, "cannot destroy controller")
   105  	default:
   106  		if errors.Cause(err) != modelcmd.ErrConnTimedOut {
   107  			logger.Debugf("unable to open api: %s", err)
   108  		}
   109  		ctx.Infof("Unable to open API: %s\n", err)
   110  		api = nil
   111  	}
   112  
   113  	// Obtain controller environ so we can clean up afterwards.
   114  	controllerEnviron, err := c.getControllerEnviron(store, controllerName, api)
   115  	if err != nil {
   116  		return errors.Annotate(err, "getting controller environ")
   117  	}
   118  
   119  	// If we were unable to connect to the API, just destroy the controller through
   120  	// the environs interface.
   121  	if api == nil {
   122  		ctx.Infof("Unable to connect to the API server. Destroying through provider.")
   123  		return environs.Destroy(controllerName, controllerEnviron, store)
   124  	}
   125  
   126  	// Attempt to destroy the controller and all environments.
   127  	err = api.DestroyController(true)
   128  	if err != nil {
   129  		ctx.Infof("Unable to destroy controller through the API: %s.  Destroying through provider.", err)
   130  		return environs.Destroy(controllerName, controllerEnviron, store)
   131  	}
   132  
   133  	ctx.Infof("Destroying controller %q\nWaiting for resources to be reclaimed", controllerName)
   134  
   135  	updateStatus := newTimedStatusUpdater(ctx, api, controllerDetails.ControllerUUID)
   136  	for ctrStatus, envsStatus := updateStatus(0); hasUnDeadModels(envsStatus); ctrStatus, envsStatus = updateStatus(2 * time.Second) {
   137  		ctx.Infof(fmtCtrStatus(ctrStatus))
   138  		for _, envStatus := range envsStatus {
   139  			ctx.Verbosef(fmtModelStatus(envStatus))
   140  		}
   141  	}
   142  
   143  	ctx.Infof("All hosted models reclaimed, cleaning up controller machines")
   144  
   145  	return environs.Destroy(controllerName, controllerEnviron, store)
   146  }