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: ¶ms.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 }