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