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  }