github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/storage/add.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package storage 5 6 import ( 7 "fmt" 8 "sort" 9 "strings" 10 11 "github.com/juju/cmd" 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/apiserver/params" 17 jujucmd "github.com/juju/juju/cmd" 18 "github.com/juju/juju/cmd/juju/common" 19 "github.com/juju/juju/cmd/modelcmd" 20 "github.com/juju/juju/storage" 21 ) 22 23 // NewAddCommand returns a command used to add unit storage. 24 func NewAddCommand() cmd.Command { 25 cmd := &addCommand{} 26 cmd.newAPIFunc = func() (StorageAddAPI, error) { 27 return cmd.NewStorageAPI() 28 } 29 return modelcmd.Wrap(cmd) 30 } 31 32 const ( 33 addCommandDoc = ` 34 Add storage instances to a unit dynamically using provided storage directives. 35 Specify a unit and a storage specification in the same format 36 as passed to juju deploy --storage=”...”. 37 38 A storage directive consists of a storage name as per charm specification 39 and storage constraints, e.g. pool, count, size. 40 41 The acceptable format for storage constraints is a comma separated 42 sequence of: POOL, COUNT, and SIZE, where 43 44 POOL identifies the storage pool. POOL can be a string 45 starting with a letter, followed by zero or more digits 46 or letters optionally separated by hyphens. 47 48 COUNT is a positive integer indicating how many instances 49 of the storage to create. If unspecified, and SIZE is 50 specified, COUNT defaults to 1. 51 52 SIZE describes the minimum size of the storage instances to 53 create. SIZE is a floating point number and multiplier from 54 the set (M, G, T, P, E, Z, Y), which are all treated as 55 powers of 1024. 56 57 Storage constraints can be optionally omitted. 58 Model default values will be used for all omitted constraint values. 59 There is no need to comma-separate omitted constraints. 60 61 Examples: 62 # Add 3 ebs storage instances for "data" storage to unit u/0: 63 64 juju add-storage u/0 data=ebs,1024,3 65 or 66 juju add-storage u/0 data=ebs,3 67 or 68 juju add-storage u/0 data=ebs,,3 69 70 71 # Add 1 storage instances for "data" storage to unit u/0 72 # using default model provider pool: 73 74 juju add-storage u/0 data=1 75 or 76 juju add-storage u/0 data 77 ` 78 addCommandAgs = `<unit name> <charm storage name>[=<storage constraints>]` 79 ) 80 81 // addCommand adds unit storage instances dynamically. 82 type addCommand struct { 83 StorageCommandBase 84 modelcmd.IAASOnlyCommand 85 unitTag names.UnitTag 86 87 // storageCons is a map of storage constraints, keyed on the storage name 88 // defined in charm storage metadata. 89 storageCons map[string]storage.Constraints 90 newAPIFunc func() (StorageAddAPI, error) 91 } 92 93 // Init implements Command.Init. 94 func (c *addCommand) Init(args []string) (err error) { 95 if len(args) < 2 { 96 return errors.New("add-storage requires a unit and a storage directive") 97 } 98 99 u := args[0] 100 if !names.IsValidUnit(u) { 101 return errors.NotValidf("unit name %q", u) 102 } 103 c.unitTag = names.NewUnitTag(u) 104 105 c.storageCons, err = storage.ParseConstraintsMap(args[1:], false) 106 return 107 } 108 109 // Info implements Command.Info. 110 func (c *addCommand) Info() *cmd.Info { 111 return jujucmd.Info(&cmd.Info{ 112 Name: "add-storage", 113 Purpose: "Adds unit storage dynamically.", 114 Doc: addCommandDoc, 115 Args: addCommandAgs, 116 }) 117 } 118 119 // Run implements Command.Run. 120 func (c *addCommand) Run(ctx *cmd.Context) (err error) { 121 api, err := c.newAPIFunc() 122 if err != nil { 123 return err 124 } 125 defer api.Close() 126 127 storages := c.createStorageAddParams() 128 results, err := api.AddToUnit(storages) 129 if err != nil { 130 if params.IsCodeUnauthorized(err) { 131 common.PermissionsMessage(ctx.Stderr, "add storage") 132 } 133 return err 134 } 135 136 var failures []string 137 // If there was a unit-related error, then all storages will get the same error. 138 // We want to collapse these - no need to repeat the same things ad nauseam. 139 collapsedFailures := set.NewStrings() 140 for i, one := range results { 141 us := storages[i] 142 if one.Error != nil { 143 const fail = "failed to add storage %q to %s: %v" 144 failures = append(failures, fmt.Sprintf(fail, us.StorageName, c.unitTag.Id(), one.Error)) 145 collapsedFailures.Add(one.Error.Error()) 146 continue 147 } 148 if one.Result == nil { 149 // Old controllers don't inform us of tag names. 150 ctx.Infof("added storage %q to %s", us.StorageName, c.unitTag.Id()) 151 continue 152 } 153 for _, tagString := range one.Result.StorageTags { 154 tag, err := names.ParseStorageTag(tagString) 155 if err != nil { 156 return errors.Trace(err) 157 } 158 ctx.Infof("added storage %s to %s", tag.Id(), c.unitTag.Id()) 159 } 160 } 161 162 if len(failures) == len(storages) { 163 // If we managed to collapse, then display these instead of the whole list. 164 if len(collapsedFailures) < len(failures) { 165 for _, one := range collapsedFailures.SortedValues() { 166 fmt.Fprintln(ctx.Stderr, one) 167 } 168 return cmd.ErrSilent 169 } 170 } 171 if len(failures) > 0 { 172 fmt.Fprintln(ctx.Stderr, strings.Join(failures, "\n")) 173 return cmd.ErrSilent 174 } 175 return nil 176 } 177 178 // StorageAddAPI defines the API methods that the storage commands use. 179 type StorageAddAPI interface { 180 Close() error 181 AddToUnit(storages []params.StorageAddParams) ([]params.AddStorageResult, error) 182 } 183 184 func (c *addCommand) createStorageAddParams() []params.StorageAddParams { 185 all := make([]params.StorageAddParams, 0, len(c.storageCons)) 186 for one, cons := range c.storageCons { 187 all = append(all, params.StorageAddParams{ 188 UnitTag: c.unitTag.String(), 189 StorageName: one, 190 Constraints: params.StorageConstraints{ 191 cons.Pool, 192 &cons.Size, 193 &cons.Count, 194 }, 195 }) 196 } 197 198 // For consistency and because we are coming from a map, 199 // ensure that collection is sorted by storage name for deterministic results. 200 sort.Sort(storageParams(all)) 201 return all 202 } 203 204 type storageParams []params.StorageAddParams 205 206 func (v storageParams) Len() int { 207 return len(v) 208 } 209 210 func (v storageParams) Swap(i, j int) { 211 v[i], v[j] = v[j], v[i] 212 } 213 214 func (v storageParams) Less(i, j int) bool { 215 return v[i].StorageName < v[j].StorageName 216 }