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  }