github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/model/destroy.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for infos.
     3  
     4  package model
     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/loggo"
    14  	"gopkg.in/juju/names.v2"
    15  
    16  	"github.com/juju/juju/api/base"
    17  	"github.com/juju/juju/api/modelmanager"
    18  	"github.com/juju/juju/apiserver/params"
    19  	jujucmd "github.com/juju/juju/cmd"
    20  	"github.com/juju/juju/cmd/juju/block"
    21  	"github.com/juju/juju/cmd/modelcmd"
    22  	"github.com/juju/juju/jujuclient"
    23  )
    24  
    25  var logger = loggo.GetLogger("juju.cmd.juju.model")
    26  
    27  // NewDestroyCommand returns a command used to destroy a model.
    28  func NewDestroyCommand() cmd.Command {
    29  	destroyCmd := &destroyCommand{}
    30  	destroyCmd.RefreshModels = destroyCmd.ModelCommandBase.RefreshModels
    31  	destroyCmd.sleepFunc = time.Sleep
    32  	return modelcmd.Wrap(
    33  		destroyCmd,
    34  		modelcmd.WrapSkipDefaultModel,
    35  		modelcmd.WrapSkipModelFlags,
    36  	)
    37  }
    38  
    39  // destroyCommand destroys the specified model.
    40  type destroyCommand struct {
    41  	modelcmd.ModelCommandBase
    42  	// RefreshModels hides the RefreshModels function defined
    43  	// in ModelCommandBase. This allows overriding for testing.
    44  	// NOTE: ideal solution would be to have the base implement a method
    45  	// like store.ModelByName which auto-refreshes.
    46  	RefreshModels func(jujuclient.ClientStore, string) error
    47  
    48  	// sleepFunc is used when calling the timed function to get model status updates.
    49  	sleepFunc func(time.Duration)
    50  
    51  	envName   string
    52  	assumeYes bool
    53  	api       DestroyModelAPI
    54  }
    55  
    56  var destroyDoc = `
    57  Destroys the specified model. This will result in the non-recoverable
    58  removal of all the units operating in the model and any resources stored
    59  there. Due to the irreversible nature of the command, it will prompt for
    60  confirmation (unless overridden with the '-y' option) before taking any
    61  action.
    62  
    63  Examples:
    64  
    65      juju destroy-model test
    66      juju destroy-model -y mymodel
    67  
    68  See also:
    69      destroy-controller
    70  `
    71  var destroyEnvMsg = `
    72  WARNING! This command will destroy the %q model.
    73  This includes all machines, applications, data and other resources.
    74  
    75  Continue [y/N]? `[1:]
    76  
    77  // DestroyModelAPI defines the methods on the modelmanager
    78  // API that the destroy command calls. It is exported for mocking in tests.
    79  type DestroyModelAPI interface {
    80  	Close() error
    81  	DestroyModel(names.ModelTag) error
    82  	ModelStatus(models ...names.ModelTag) ([]base.ModelStatus, error)
    83  }
    84  
    85  // Info implements Command.Info.
    86  func (c *destroyCommand) Info() *cmd.Info {
    87  	return &cmd.Info{
    88  		Name:    "destroy-model",
    89  		Args:    "[<controller name>:]<model name>",
    90  		Purpose: "Terminate all machines and resources for a non-controller model.",
    91  		Doc:     destroyDoc,
    92  	}
    93  }
    94  
    95  // SetFlags implements Command.SetFlags.
    96  func (c *destroyCommand) SetFlags(f *gnuflag.FlagSet) {
    97  	c.ModelCommandBase.SetFlags(f)
    98  	f.BoolVar(&c.assumeYes, "y", false, "Do not prompt for confirmation")
    99  	f.BoolVar(&c.assumeYes, "yes", false, "")
   100  }
   101  
   102  // Init implements Command.Init.
   103  func (c *destroyCommand) Init(args []string) error {
   104  	switch len(args) {
   105  	case 0:
   106  		return errors.New("no model specified")
   107  	case 1:
   108  		return c.SetModelName(args[0])
   109  	default:
   110  		return cmd.CheckEmpty(args[1:])
   111  	}
   112  }
   113  
   114  func (c *destroyCommand) getAPI() (DestroyModelAPI, error) {
   115  	if c.api != nil {
   116  		return c.api, nil
   117  	}
   118  	root, err := c.NewControllerAPIRoot()
   119  	if err != nil {
   120  		return nil, errors.Trace(err)
   121  	}
   122  	return modelmanager.NewClient(root), nil
   123  }
   124  
   125  // Run implements Command.Run
   126  func (c *destroyCommand) Run(ctx *cmd.Context) error {
   127  	store := c.ClientStore()
   128  	controllerName := c.ControllerName()
   129  	modelName := c.ModelName()
   130  
   131  	controllerDetails, err := store.ControllerByName(controllerName)
   132  	if err != nil {
   133  		return errors.Annotate(err, "cannot read controller details")
   134  	}
   135  	modelDetails, err := store.ModelByName(controllerName, modelName)
   136  	if errors.IsNotFound(err) {
   137  		if err := c.RefreshModels(store, controllerName); err != nil {
   138  			return errors.Annotate(err, "refreshing models cache")
   139  		}
   140  		// Now try again.
   141  		modelDetails, err = store.ModelByName(controllerName, modelName)
   142  	}
   143  	if err != nil {
   144  		return errors.Annotate(err, "cannot read model info")
   145  	}
   146  
   147  	if modelDetails.ModelUUID == controllerDetails.ControllerUUID {
   148  		return errors.Errorf("%q is a controller; use 'juju destroy-controller' to destroy it", modelName)
   149  	}
   150  
   151  	if !c.assumeYes {
   152  		fmt.Fprintf(ctx.Stdout, destroyEnvMsg, modelName)
   153  
   154  		if err := jujucmd.UserConfirmYes(ctx); err != nil {
   155  			return errors.Annotate(err, "model destruction")
   156  		}
   157  	}
   158  
   159  	// Attempt to connect to the API.  If we can't, fail the destroy.
   160  	api, err := c.getAPI()
   161  	if err != nil {
   162  		return errors.Annotate(err, "cannot connect to API")
   163  	}
   164  	defer api.Close()
   165  
   166  	// Attempt to destroy the model.
   167  	ctx.Infof("Destroying model")
   168  	err = api.DestroyModel(names.NewModelTag(modelDetails.ModelUUID))
   169  	if err != nil {
   170  		return c.handleError(errors.Annotate(err, "cannot destroy model"), modelName)
   171  	}
   172  
   173  	// Wait for model to be destroyed.
   174  	const modelStatusPollWait = 2 * time.Second
   175  	modelStatus := newTimedModelStatus(ctx, api, names.NewModelTag(modelDetails.ModelUUID), c.sleepFunc)
   176  	modelData := modelStatus(0)
   177  	for modelData != nil {
   178  		ctx.Infof(formatDestroyModelInfo(modelData) + "...")
   179  		modelData = modelStatus(modelStatusPollWait)
   180  	}
   181  
   182  	err = store.RemoveModel(controllerName, modelName)
   183  	if err != nil && !errors.IsNotFound(err) {
   184  		return errors.Trace(err)
   185  	}
   186  	return nil
   187  }
   188  
   189  type modelData struct {
   190  	machineCount     int
   191  	applicationCount int
   192  }
   193  
   194  // newTimedModelStatus returns a function which waits a given period of time
   195  // before querying the API server for the status of a model.
   196  func newTimedModelStatus(ctx *cmd.Context, api DestroyModelAPI, tag names.ModelTag, sleepFunc func(time.Duration)) func(time.Duration) *modelData {
   197  	return func(wait time.Duration) *modelData {
   198  		sleepFunc(wait)
   199  		status, err := api.ModelStatus(tag)
   200  		if err != nil {
   201  			if params.ErrCode(err) != params.CodeNotFound {
   202  				ctx.Infof("Unable to get the model status from the API: %v.", err)
   203  			}
   204  			return nil
   205  		}
   206  		if l := len(status); l != 1 {
   207  			ctx.Infof("error finding model status: expected one result, got %d", l)
   208  			return nil
   209  		}
   210  		return &modelData{
   211  			machineCount:     status[0].HostedMachineCount,
   212  			applicationCount: status[0].ServiceCount,
   213  		}
   214  	}
   215  }
   216  
   217  func formatDestroyModelInfo(data *modelData) string {
   218  	out := "Waiting on model to be removed"
   219  	if data.machineCount == 0 && data.applicationCount == 0 {
   220  		return out
   221  	}
   222  	if data.machineCount > 0 {
   223  		out += fmt.Sprintf(", %d machine(s)", data.machineCount)
   224  	}
   225  	if data.applicationCount > 0 {
   226  		out += fmt.Sprintf(", %d application(s)", data.applicationCount)
   227  	}
   228  	return out
   229  }
   230  
   231  func (c *destroyCommand) handleError(err error, modelName string) error {
   232  	if err == nil {
   233  		return nil
   234  	}
   235  	if params.IsCodeOperationBlocked(err) {
   236  		return block.ProcessBlockedError(err, block.BlockDestroy)
   237  	}
   238  	logger.Errorf(`failed to destroy model %q`, modelName)
   239  	return err
   240  }