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