github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/application/removeunit.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"github.com/juju/cmd"
     8  	"github.com/juju/collections/set"
     9  	"github.com/juju/errors"
    10  	"github.com/juju/gnuflag"
    11  	"gopkg.in/juju/names.v2"
    12  
    13  	"github.com/juju/juju/api/application"
    14  	"github.com/juju/juju/api/storage"
    15  	jujucmd "github.com/juju/juju/cmd"
    16  	"github.com/juju/juju/cmd/juju/block"
    17  	"github.com/juju/juju/cmd/modelcmd"
    18  	"github.com/juju/juju/core/model"
    19  )
    20  
    21  // NewRemoveUnitCommand returns a command which removes an application's units.
    22  func NewRemoveUnitCommand() modelcmd.ModelCommand {
    23  	return modelcmd.Wrap(&removeUnitCommand{})
    24  }
    25  
    26  // removeUnitCommand is responsible for destroying application units.
    27  type removeUnitCommand struct {
    28  	modelcmd.ModelCommandBase
    29  	DestroyStorage bool
    30  	NumUnits       int
    31  	EntityNames    []string
    32  	api            removeApplicationAPI
    33  
    34  	unknownModel bool
    35  }
    36  
    37  const removeUnitDoc = `
    38  Remove application units from the model.
    39  
    40  The usage of this command differs depending on whether it is being used on a
    41  Kubernetes or cloud model.
    42  
    43  Removing all units of a application is not equivalent to removing the
    44  application itself; for that, the ` + "`juju remove-application`" + ` command
    45  is used.
    46  
    47  For Kubernetes models only a single application can be supplied and only the
    48  --num-units argument supported.
    49  Specific units cannot be targeted for removal as that is handled by Kubernetes,
    50  instead the total number of units to be removed is specified.
    51  
    52  Examples:
    53      juju remove-unit wordpress --num-units 2
    54  
    55  For cloud models specific units can be targeted for removal.
    56  Units of a application are numbered in sequence upon creation. For example, the
    57  fourth unit of wordpress will be designated "wordpress/3". These identifiers
    58  can be supplied in a space delimited list to remove unwanted units from the
    59  model.
    60  
    61  Juju will also remove the machine if the removed unit was the only unit left
    62  on that machine (including units in containers).
    63  
    64  Examples:
    65  
    66      juju remove-unit wordpress/2 wordpress/3 wordpress/4
    67  
    68      juju remove-unit wordpress/2 --destroy-storage
    69  
    70  See also:
    71      remove-application
    72      scale-application
    73  `
    74  
    75  func (c *removeUnitCommand) Info() *cmd.Info {
    76  	return jujucmd.Info(&cmd.Info{
    77  		Name:    "remove-unit",
    78  		Args:    "<unit> [...] | <application>",
    79  		Purpose: "Remove application units from the model.",
    80  		Doc:     removeUnitDoc,
    81  	})
    82  }
    83  
    84  func (c *removeUnitCommand) SetFlags(f *gnuflag.FlagSet) {
    85  	c.ModelCommandBase.SetFlags(f)
    86  	f.IntVar(&c.NumUnits, "num-units", 0, "Number of units to remove (kubernetes models only)")
    87  	f.BoolVar(&c.DestroyStorage, "destroy-storage", false, "Destroy storage attached to the unit")
    88  }
    89  
    90  func (c *removeUnitCommand) Init(args []string) error {
    91  	c.EntityNames = args
    92  	if err := c.validateArgsByModelType(); err != nil {
    93  		if !errors.IsNotFound(err) {
    94  			return errors.Trace(err)
    95  		}
    96  
    97  		c.unknownModel = true
    98  	}
    99  	return nil
   100  }
   101  
   102  func (c *removeUnitCommand) validateArgsByModelType() error {
   103  	modelType, err := c.ModelType()
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if modelType == model.CAAS {
   108  		return c.validateCAASRemoval()
   109  	}
   110  
   111  	return c.validateIAASRemoval()
   112  }
   113  
   114  func (c *removeUnitCommand) validateCAASRemoval() error {
   115  	if c.DestroyStorage {
   116  		return errors.New("Kubernetes models only support --num-units")
   117  	}
   118  	if len(c.EntityNames) != 1 {
   119  		return errors.Errorf("only single application supported")
   120  	}
   121  	if !names.IsValidApplication(c.EntityNames[0]) {
   122  		return errors.NotValidf("application name %q", c.EntityNames[0])
   123  	}
   124  	if c.NumUnits <= 0 {
   125  		return errors.NotValidf("removing %d units", c.NumUnits)
   126  	}
   127  
   128  	return nil
   129  }
   130  
   131  func (c *removeUnitCommand) validateIAASRemoval() error {
   132  	if c.NumUnits != 0 {
   133  		return errors.NotValidf("--num-units for non kubernetes models")
   134  	}
   135  	if len(c.EntityNames) == 0 {
   136  		return errors.Errorf("no units specified")
   137  	}
   138  	for _, name := range c.EntityNames {
   139  		if !names.IsValidUnit(name) {
   140  			return errors.Errorf("invalid unit name %q", name)
   141  		}
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  func (c *removeUnitCommand) getAPI() (removeApplicationAPI, int, error) {
   148  	if c.api != nil {
   149  		return c.api, c.api.BestAPIVersion(), nil
   150  	}
   151  	root, err := c.NewAPIRoot()
   152  	if err != nil {
   153  		return nil, -1, errors.Trace(err)
   154  	}
   155  	api := application.NewClient(root)
   156  	return api, api.BestAPIVersion(), nil
   157  }
   158  
   159  func (c *removeUnitCommand) getStorageAPI() (storageAPI, error) {
   160  	root, err := c.NewAPIRoot()
   161  	if err != nil {
   162  		return nil, errors.Trace(err)
   163  	}
   164  	return storage.NewClient(root), nil
   165  }
   166  
   167  func (c *removeUnitCommand) unitsHaveStorage(unitNames []string) (bool, error) {
   168  	client, err := c.getStorageAPI()
   169  	if err != nil {
   170  		return false, errors.Trace(err)
   171  	}
   172  	defer client.Close()
   173  
   174  	storage, err := client.ListStorageDetails()
   175  	if err != nil {
   176  		return false, errors.Trace(err)
   177  	}
   178  	namesSet := set.NewStrings(unitNames...)
   179  	for _, s := range storage {
   180  		if s.OwnerTag == "" {
   181  			continue
   182  		}
   183  		owner, err := names.ParseTag(s.OwnerTag)
   184  		if err != nil {
   185  			return false, errors.Trace(err)
   186  		}
   187  		if owner.Kind() == names.UnitTagKind && namesSet.Contains(owner.Id()) {
   188  			return true, nil
   189  		}
   190  	}
   191  	return false, nil
   192  }
   193  
   194  // Run connects to the environment specified on the command line and destroys
   195  // units therein.
   196  func (c *removeUnitCommand) Run(ctx *cmd.Context) error {
   197  	client, apiVersion, err := c.getAPI()
   198  	if err != nil {
   199  		return err
   200  	}
   201  	defer client.Close()
   202  
   203  	if apiVersion < 4 {
   204  		return c.removeUnitsDeprecated(ctx, client)
   205  	}
   206  
   207  	if err := c.validateArgsByModelType(); err != nil {
   208  		return errors.Trace(err)
   209  	}
   210  
   211  	modelType, err := c.ModelType()
   212  	if err != nil {
   213  		return err
   214  	}
   215  	if modelType == model.CAAS {
   216  		return c.removeCaasUnits(ctx, client)
   217  	}
   218  
   219  	if c.DestroyStorage && apiVersion < 5 {
   220  		return errors.New("--destroy-storage is not supported by this controller")
   221  	}
   222  	return c.removeUnits(ctx, client)
   223  }
   224  
   225  // TODO(axw) 2017-03-16 #1673323
   226  // Drop this in Juju 3.0.
   227  func (c *removeUnitCommand) removeUnitsDeprecated(ctx *cmd.Context, client removeApplicationAPI) error {
   228  	err := client.DestroyUnitsDeprecated(c.EntityNames...)
   229  	return block.ProcessBlockedError(err, block.BlockRemove)
   230  }
   231  
   232  func (c *removeUnitCommand) removeUnits(ctx *cmd.Context, client removeApplicationAPI) error {
   233  	results, err := client.DestroyUnits(application.DestroyUnitsParams{
   234  		Units:          c.EntityNames,
   235  		DestroyStorage: c.DestroyStorage,
   236  	})
   237  	if err != nil {
   238  		return block.ProcessBlockedError(err, block.BlockRemove)
   239  	}
   240  	anyFailed := false
   241  	for i, name := range c.EntityNames {
   242  		result := results[i]
   243  		if result.Error != nil {
   244  			anyFailed = true
   245  			ctx.Infof("removing unit %s failed: %s", name, result.Error)
   246  			continue
   247  		}
   248  		ctx.Infof("removing unit %s", name)
   249  		for _, entity := range result.Info.DestroyedStorage {
   250  			storageTag, err := names.ParseStorageTag(entity.Tag)
   251  			if err != nil {
   252  				logger.Warningf("%s", err)
   253  				continue
   254  			}
   255  			ctx.Infof("- will remove %s", names.ReadableString(storageTag))
   256  		}
   257  		for _, entity := range result.Info.DetachedStorage {
   258  			storageTag, err := names.ParseStorageTag(entity.Tag)
   259  			if err != nil {
   260  				logger.Warningf("%s", err)
   261  				continue
   262  			}
   263  			ctx.Infof("- will detach %s", names.ReadableString(storageTag))
   264  		}
   265  	}
   266  	if anyFailed {
   267  		return cmd.ErrSilent
   268  	}
   269  	return nil
   270  }
   271  
   272  func (c *removeUnitCommand) removeCaasUnits(ctx *cmd.Context, client removeApplicationAPI) error {
   273  	result, err := client.ScaleApplication(application.ScaleApplicationParams{
   274  		ApplicationName: c.EntityNames[0],
   275  		ScaleChange:     -c.NumUnits,
   276  	})
   277  	if err != nil {
   278  		return block.ProcessBlockedError(err, block.BlockRemove)
   279  	}
   280  	ctx.Infof("scaling down to %d units", result.Info.Scale)
   281  	return nil
   282  }