github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/gnuflag" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/api/machinemanager" 16 "github.com/juju/juju/api/modelconfig" 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/cmd/juju/block" 19 "github.com/juju/juju/cmd/juju/common" 20 "github.com/juju/juju/cmd/modelcmd" 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/environs/config" 23 "github.com/juju/juju/environs/manual" 24 "github.com/juju/juju/instance" 25 "github.com/juju/juju/state/multiwatcher" 26 "github.com/juju/juju/storage" 27 ) 28 29 var addMachineDoc = ` 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. "lxd"), 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 lxd (starts a new machine with an lxd container) 64 juju add-machine lxd -n 2 (starts 2 new machines with an lxd container) 65 juju add-machine lxd:4 (starts a new lxd 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 remove-machine 73 ` 74 75 func init() { 76 containerTypes := make([]string, len(instance.ContainerTypes)) 77 for i, t := range instance.ContainerTypes { 78 containerTypes[i] = string(t) 79 } 80 addMachineDoc = strings.Replace( 81 addMachineDoc, 82 "$CONTAINER_TYPES$", 83 strings.Join(containerTypes, ", "), 84 -1, 85 ) 86 } 87 88 // NewAddCommand returns a command that adds a machine to a model. 89 func NewAddCommand() cmd.Command { 90 return modelcmd.Wrap(&addCommand{}) 91 } 92 93 // addCommand starts a new machine and registers it in the model. 94 type addCommand struct { 95 modelcmd.ModelCommandBase 96 api AddMachineAPI 97 modelConfigAPI ModelConfigAPI 98 machineManagerAPI MachineManagerAPI 99 // If specified, use this series, else use the model default-series 100 Series string 101 // If specified, these constraints are merged with those already in the model. 102 Constraints constraints.Value 103 // If specified, these constraints are merged with those already in the model. 104 ConstraintsStr string 105 // Placement is passed verbatim to the API, to be parsed and evaluated server-side. 106 Placement *instance.Placement 107 // NumMachines is the number of machines to add. 108 NumMachines int 109 // Disks describes disks that are to be attached to the machine. 110 Disks []storage.Constraints 111 } 112 113 func (c *addCommand) Info() *cmd.Info { 114 return &cmd.Info{ 115 Name: "add-machine", 116 Args: "[<container>:machine | <container> | ssh:[user@]host | placement]", 117 Purpose: "Start a new, empty machine and optionally a container, or add a container to a machine.", 118 Doc: addMachineDoc, 119 } 120 } 121 122 func (c *addCommand) SetFlags(f *gnuflag.FlagSet) { 123 c.ModelCommandBase.SetFlags(f) 124 f.StringVar(&c.Series, "series", "", "The charm series") 125 f.IntVar(&c.NumMachines, "n", 1, "The number of machines to add") 126 f.StringVar(&c.ConstraintsStr, "constraints", "", "Additional machine constraints") 127 f.Var(disksFlag{&c.Disks}, "disks", "Constraints for disks to attach to the machine") 128 } 129 130 func (c *addCommand) Init(args []string) error { 131 if c.Constraints.Container != nil { 132 return errors.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 = "model-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 errors.New("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 Close() error 155 ForceDestroyMachines(machines ...string) error 156 ModelUUID() (string, bool) 157 ProvisioningScript(params.ProvisioningScriptParams) (script string, err error) 158 } 159 160 type ModelConfigAPI interface { 161 ModelGet() (map[string]interface{}, error) 162 Close() error 163 } 164 165 type MachineManagerAPI interface { 166 AddMachines([]params.AddMachineParams) ([]params.AddMachinesResult, error) 167 BestAPIVersion() int 168 Close() error 169 } 170 171 var manualProvisioner = manual.ProvisionMachine 172 173 func (c *addCommand) getClientAPI() (AddMachineAPI, error) { 174 if c.api != nil { 175 return c.api, nil 176 } 177 return c.NewAPIClient() 178 } 179 180 func (c *addCommand) getModelConfigAPI() (ModelConfigAPI, error) { 181 if c.modelConfigAPI != nil { 182 return c.modelConfigAPI, nil 183 } 184 api, err := c.NewAPIRoot() 185 if err != nil { 186 return nil, errors.Annotate(err, "opening API connection") 187 } 188 return modelconfig.NewClient(api), nil 189 190 } 191 192 func (c *addCommand) NewMachineManagerClient() (*machinemanager.Client, error) { 193 root, err := c.NewAPIRoot() 194 if err != nil { 195 return nil, errors.Trace(err) 196 } 197 return machinemanager.NewClient(root), nil 198 } 199 200 func (c *addCommand) getMachineManagerAPI() (MachineManagerAPI, error) { 201 if c.machineManagerAPI != nil { 202 return c.machineManagerAPI, nil 203 } 204 return c.NewMachineManagerClient() 205 } 206 207 func (c *addCommand) Run(ctx *cmd.Context) error { 208 var err error 209 c.Constraints, err = common.ParseConstraints(ctx, c.ConstraintsStr) 210 if err != nil { 211 return err 212 } 213 client, err := c.getClientAPI() 214 if err != nil { 215 return errors.Trace(err) 216 } 217 defer client.Close() 218 219 var machineManager MachineManagerAPI 220 if len(c.Disks) > 0 { 221 machineManager, err = c.getMachineManagerAPI() 222 if err != nil { 223 return errors.Trace(err) 224 } 225 defer machineManager.Close() 226 if machineManager.BestAPIVersion() < 1 { 227 return errors.New("cannot add machines with disks: not supported by the API server") 228 } 229 } 230 231 logger.Infof("load config") 232 modelConfigClient, err := c.getModelConfigAPI() 233 if err != nil { 234 return errors.Trace(err) 235 } 236 defer modelConfigClient.Close() 237 configAttrs, err := modelConfigClient.ModelGet() 238 if err != nil { 239 return errors.Trace(err) 240 } 241 config, err := config.New(config.NoDefaults, configAttrs) 242 if err != nil { 243 return errors.Trace(err) 244 } 245 246 if c.Placement != nil && c.Placement.Scope == "ssh" { 247 logger.Infof("manual provisioning") 248 authKeys, err := common.ReadAuthorizedKeys(ctx, "") 249 if err != nil { 250 return errors.Annotate(err, "reading authorized-keys") 251 } 252 args := manual.ProvisionMachineArgs{ 253 Host: c.Placement.Directive, 254 Client: client, 255 Stdin: ctx.Stdin, 256 Stdout: ctx.Stdout, 257 Stderr: ctx.Stderr, 258 AuthorizedKeys: authKeys, 259 UpdateBehavior: ¶ms.UpdateBehavior{ 260 config.EnableOSRefreshUpdate(), 261 config.EnableOSUpgrade(), 262 }, 263 } 264 machineId, err := manualProvisioner(args) 265 if err == nil { 266 ctx.Infof("created machine %v", machineId) 267 } 268 return err 269 } 270 271 logger.Infof("model provisioning") 272 if c.Placement != nil && c.Placement.Scope == "model-uuid" { 273 uuid, ok := client.ModelUUID() 274 if !ok { 275 return errors.New("API connection is controller-only (should never happen)") 276 } 277 c.Placement.Scope = uuid 278 } 279 280 if c.Placement != nil && c.Placement.Scope == instance.MachineScope { 281 // It does not make sense to add-machine <id>. 282 return errors.Errorf("machine-id cannot be specified when adding machines") 283 } 284 285 jobs := []multiwatcher.MachineJob{multiwatcher.JobHostUnits} 286 287 machineParams := params.AddMachineParams{ 288 Placement: c.Placement, 289 Series: c.Series, 290 Constraints: c.Constraints, 291 Jobs: jobs, 292 Disks: c.Disks, 293 } 294 machines := make([]params.AddMachineParams, c.NumMachines) 295 for i := 0; i < c.NumMachines; i++ { 296 machines[i] = machineParams 297 } 298 299 var results []params.AddMachinesResult 300 // If storage is specified, we attempt to use a new API on the service facade. 301 if len(c.Disks) > 0 { 302 results, err = machineManager.AddMachines(machines) 303 } else { 304 results, err = client.AddMachines(machines) 305 } 306 if params.IsCodeOperationBlocked(err) { 307 return block.ProcessBlockedError(err, block.BlockChange) 308 } 309 if err != nil { 310 return errors.Trace(err) 311 } 312 313 errs := []error{} 314 for _, machineInfo := range results { 315 if machineInfo.Error != nil { 316 errs = append(errs, machineInfo.Error) 317 continue 318 } 319 machineId := machineInfo.Machine 320 321 if names.IsContainerMachine(machineId) { 322 ctx.Infof("created container %v", machineId) 323 } else { 324 ctx.Infof("created machine %v", machineId) 325 } 326 } 327 if len(errs) == 1 { 328 fmt.Fprint(ctx.Stderr, "failed to create 1 machine\n") 329 return errs[0] 330 } 331 if len(errs) > 1 { 332 fmt.Fprintf(ctx.Stderr, "failed to create %d machines\n", len(errs)) 333 returnErr := []string{} 334 for _, e := range errs { 335 returnErr = append(returnErr, e.Error()) 336 } 337 return errors.New(strings.Join(returnErr, ", ")) 338 } 339 return nil 340 }