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 }