github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cmd/juju/machine/add.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package machine
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"github.com/juju/cmd"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/names"
    13  	"launchpad.net/gnuflag"
    14  
    15  	"github.com/juju/juju/api/machinemanager"
    16  	"github.com/juju/juju/apiserver/params"
    17  	"github.com/juju/juju/cmd/juju/block"
    18  	"github.com/juju/juju/cmd/modelcmd"
    19  	"github.com/juju/juju/constraints"
    20  	"github.com/juju/juju/environs/config"
    21  	"github.com/juju/juju/environs/manual"
    22  	"github.com/juju/juju/instance"
    23  	"github.com/juju/juju/provider"
    24  	"github.com/juju/juju/state/multiwatcher"
    25  	"github.com/juju/juju/storage"
    26  )
    27  
    28  var addMachineDoc = `
    29  
    30  Juju supports adding machines using provider-specific machine instances
    31  (EC2 instances, OpenStack servers, MAAS nodes, etc.); existing machines
    32  running a supported operating system (see "manual provisioning" below),
    33  and containers on machines. Machines are created in a clean state and
    34  ready to have units deployed.
    35  
    36  Without any parameters, add machine will allocate a new provider-specific
    37  machine (multiple, if "-n" is provided). When adding a new machine, you
    38  may specify constraints for the machine to be provisioned; the provider
    39  will interpret these constraints in order to decide what kind of machine
    40  to allocate.
    41  
    42  If a container type is specified (e.g. "lxc"), then add machine will
    43  allocate a container of that type on a new provider-specific machine. It is
    44  also possible to add containers to existing machines using the format
    45  <container type>:<machine number>. Constraints cannot be combined with
    46  deploying a container to an existing machine. The currently supported
    47  container types are: $CONTAINER_TYPES$.
    48  
    49  Manual provisioning is the process of installing Juju on an existing machine
    50  and bringing it under Juju's management; currently this requires that the
    51  machine be running Ubuntu, that it be accessible via SSH, and be running on
    52  the same network as the API server.
    53  
    54  It is possible to override or augment constraints by passing provider-specific
    55  "placement directives" as an argument; these give the provider additional
    56  information about how to allocate the machine. For example, one can direct the
    57  MAAS provider to acquire a particular node by specifying its hostname.
    58  For more information on placement directives, see "juju help placement".
    59  
    60  Examples:
    61     juju add-machine                      (starts a new machine)
    62     juju add-machine -n 2                 (starts 2 new machines)
    63     juju add-machine lxc                  (starts a new machine with an lxc container)
    64     juju add-machine lxc -n 2             (starts 2 new machines with an lxc container)
    65     juju add-machine lxc:4                (starts a new lxc container on machine 4)
    66     juju add-machine --constraints mem=8G (starts a machine with at least 8GB RAM)
    67     juju add-machine ssh:user@10.10.0.3   (manually provisions a machine with ssh)
    68     juju add-machine zone=us-east-1a      (start a machine in zone us-east-1a on AWS)
    69     juju add-machine maas2.name           (acquire machine maas2.name on MAAS)
    70  
    71  See Also:
    72     juju help constraints
    73     juju help placement
    74     juju help remove-machine
    75  `
    76  
    77  func init() {
    78  	containerTypes := make([]string, len(instance.ContainerTypes))
    79  	for i, t := range instance.ContainerTypes {
    80  		containerTypes[i] = string(t)
    81  	}
    82  	addMachineDoc = strings.Replace(
    83  		addMachineDoc,
    84  		"$CONTAINER_TYPES$",
    85  		strings.Join(containerTypes, ", "),
    86  		-1,
    87  	)
    88  }
    89  
    90  // NewAddCommand returns a command that adds a machine to a model.
    91  func NewAddCommand() cmd.Command {
    92  	return modelcmd.Wrap(&addCommand{})
    93  }
    94  
    95  // addCommand starts a new machine and registers it in the model.
    96  type addCommand struct {
    97  	modelcmd.ModelCommandBase
    98  	api               AddMachineAPI
    99  	machineManagerAPI MachineManagerAPI
   100  	// If specified, use this series, else use the model default-series
   101  	Series string
   102  	// If specified, these constraints are merged with those already in the model.
   103  	Constraints constraints.Value
   104  	// Placement is passed verbatim to the API, to be parsed and evaluated server-side.
   105  	Placement *instance.Placement
   106  	// NumMachines is the number of machines to add.
   107  	NumMachines int
   108  	// Disks describes disks that are to be attached to the machine.
   109  	Disks []storage.Constraints
   110  }
   111  
   112  func (c *addCommand) Info() *cmd.Info {
   113  	return &cmd.Info{
   114  		Name:    "add-machine",
   115  		Args:    "[<container>:machine | <container> | ssh:[user@]host | placement]",
   116  		Purpose: "start a new, empty machine and optionally a container, or add a container to a machine",
   117  		Doc:     addMachineDoc,
   118  		Aliases: []string{"add-machines"},
   119  	}
   120  }
   121  
   122  func (c *addCommand) SetFlags(f *gnuflag.FlagSet) {
   123  	f.StringVar(&c.Series, "series", "", "the charm series")
   124  	f.IntVar(&c.NumMachines, "n", 1, "The number of machines to add")
   125  	f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "additional machine constraints")
   126  	f.Var(disksFlag{&c.Disks}, "disks", "constraints for disks to attach to the machine")
   127  }
   128  
   129  func (c *addCommand) Init(args []string) error {
   130  	if c.Constraints.Container != nil {
   131  		return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container)
   132  	}
   133  	placement, err := cmd.ZeroOrOneArgs(args)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	c.Placement, err = instance.ParsePlacement(placement)
   138  	if err == instance.ErrPlacementScopeMissing {
   139  		placement = "model-uuid" + ":" + placement
   140  		c.Placement, err = instance.ParsePlacement(placement)
   141  	}
   142  	if err != nil {
   143  		return err
   144  	}
   145  	if c.NumMachines > 1 && c.Placement != nil && c.Placement.Directive != "" {
   146  		return fmt.Errorf("cannot use -n when specifying a placement directive")
   147  	}
   148  	return nil
   149  }
   150  
   151  type AddMachineAPI interface {
   152  	AddMachines([]params.AddMachineParams) ([]params.AddMachinesResult, error)
   153  	Close() error
   154  	ForceDestroyMachines(machines ...string) error
   155  	ModelGet() (map[string]interface{}, error)
   156  	ModelUUID() string
   157  	ProvisioningScript(params.ProvisioningScriptParams) (script string, err error)
   158  }
   159  
   160  type MachineManagerAPI interface {
   161  	AddMachines([]params.AddMachineParams) ([]params.AddMachinesResult, error)
   162  	BestAPIVersion() int
   163  	Close() error
   164  }
   165  
   166  var manualProvisioner = manual.ProvisionMachine
   167  
   168  func (c *addCommand) getClientAPI() (AddMachineAPI, error) {
   169  	if c.api != nil {
   170  		return c.api, nil
   171  	}
   172  	return c.NewAPIClient()
   173  }
   174  
   175  func (c *addCommand) NewMachineManagerClient() (*machinemanager.Client, error) {
   176  	root, err := c.NewAPIRoot()
   177  	if err != nil {
   178  		return nil, errors.Trace(err)
   179  	}
   180  	return machinemanager.NewClient(root), nil
   181  }
   182  
   183  func (c *addCommand) getMachineManagerAPI() (MachineManagerAPI, error) {
   184  	if c.machineManagerAPI != nil {
   185  		return c.machineManagerAPI, nil
   186  	}
   187  	return c.NewMachineManagerClient()
   188  }
   189  
   190  func (c *addCommand) Run(ctx *cmd.Context) error {
   191  	client, err := c.getClientAPI()
   192  	if err != nil {
   193  		return errors.Trace(err)
   194  	}
   195  	defer client.Close()
   196  
   197  	var machineManager MachineManagerAPI
   198  	if len(c.Disks) > 0 {
   199  		machineManager, err = c.getMachineManagerAPI()
   200  		if err != nil {
   201  			return errors.Trace(err)
   202  		}
   203  		defer machineManager.Close()
   204  		if machineManager.BestAPIVersion() < 1 {
   205  			return errors.New("cannot add machines with disks: not supported by the API server")
   206  		}
   207  	}
   208  
   209  	logger.Infof("load config")
   210  	configAttrs, err := client.ModelGet()
   211  	if err != nil {
   212  		return errors.Trace(err)
   213  	}
   214  	config, err := config.New(config.NoDefaults, configAttrs)
   215  	if err != nil {
   216  		return errors.Trace(err)
   217  	}
   218  
   219  	if c.Placement != nil && c.Placement.Scope == "ssh" {
   220  		logger.Infof("manual provisioning")
   221  		args := manual.ProvisionMachineArgs{
   222  			Host:   c.Placement.Directive,
   223  			Client: client,
   224  			Stdin:  ctx.Stdin,
   225  			Stdout: ctx.Stdout,
   226  			Stderr: ctx.Stderr,
   227  			UpdateBehavior: &params.UpdateBehavior{
   228  				config.EnableOSRefreshUpdate(),
   229  				config.EnableOSUpgrade(),
   230  			},
   231  		}
   232  		machineId, err := manualProvisioner(args)
   233  		if err == nil {
   234  			ctx.Infof("created machine %v", machineId)
   235  		}
   236  		return err
   237  	}
   238  
   239  	logger.Infof("model provisioning")
   240  	if c.Placement != nil && c.Placement.Scope == "model-uuid" {
   241  		c.Placement.Scope = client.ModelUUID()
   242  	}
   243  
   244  	if c.Placement != nil && c.Placement.Scope == instance.MachineScope {
   245  		// It does not make sense to add-machine <id>.
   246  		return fmt.Errorf("machine-id cannot be specified when adding machines")
   247  	}
   248  
   249  	jobs := []multiwatcher.MachineJob{multiwatcher.JobHostUnits}
   250  
   251  	// In case of MAAS and Joyent JobManageNetworking is not added
   252  	// to ensure the non-intrusive start of a networker like above
   253  	// for the manual provisioning. See this related joyent bug
   254  	// http://pad.lv/1401423
   255  	if config.Type() != provider.MAAS && config.Type() != provider.Joyent {
   256  		jobs = append(jobs, multiwatcher.JobManageNetworking)
   257  	}
   258  
   259  	machineParams := params.AddMachineParams{
   260  		Placement:   c.Placement,
   261  		Series:      c.Series,
   262  		Constraints: c.Constraints,
   263  		Jobs:        jobs,
   264  		Disks:       c.Disks,
   265  	}
   266  	machines := make([]params.AddMachineParams, c.NumMachines)
   267  	for i := 0; i < c.NumMachines; i++ {
   268  		machines[i] = machineParams
   269  	}
   270  
   271  	var results []params.AddMachinesResult
   272  	// If storage is specified, we attempt to use a new API on the service facade.
   273  	if len(c.Disks) > 0 {
   274  		results, err = machineManager.AddMachines(machines)
   275  	} else {
   276  		results, err = client.AddMachines(machines)
   277  	}
   278  	if params.IsCodeOperationBlocked(err) {
   279  		return block.ProcessBlockedError(err, block.BlockChange)
   280  	}
   281  	if err != nil {
   282  		return errors.Trace(err)
   283  	}
   284  
   285  	errs := []error{}
   286  	for _, machineInfo := range results {
   287  		if machineInfo.Error != nil {
   288  			errs = append(errs, machineInfo.Error)
   289  			continue
   290  		}
   291  		machineId := machineInfo.Machine
   292  
   293  		if names.IsContainerMachine(machineId) {
   294  			ctx.Infof("created container %v", machineId)
   295  		} else {
   296  			ctx.Infof("created machine %v", machineId)
   297  		}
   298  	}
   299  	if len(errs) == 1 {
   300  		fmt.Fprintf(ctx.Stderr, "failed to create 1 machine\n")
   301  		return errs[0]
   302  	}
   303  	if len(errs) > 1 {
   304  		fmt.Fprintf(ctx.Stderr, "failed to create %d machines\n", len(errs))
   305  		returnErr := []string{}
   306  		for _, e := range errs {
   307  			returnErr = append(returnErr, e.Error())
   308  		}
   309  		return errors.New(strings.Join(returnErr, ", "))
   310  	}
   311  	return nil
   312  }