github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 if params.IsCodeUnauthorized(err) { 240 common.PermissionsMessage(ctx.Stderr, "add a machine to this model") 241 } 242 return errors.Trace(err) 243 } 244 config, err := config.New(config.NoDefaults, configAttrs) 245 if err != nil { 246 return errors.Trace(err) 247 } 248 249 if c.Placement != nil && c.Placement.Scope == "ssh" { 250 logger.Infof("manual provisioning") 251 authKeys, err := common.ReadAuthorizedKeys(ctx, "") 252 if err != nil { 253 return errors.Annotate(err, "reading authorized-keys") 254 } 255 args := manual.ProvisionMachineArgs{ 256 Host: c.Placement.Directive, 257 Client: client, 258 Stdin: ctx.Stdin, 259 Stdout: ctx.Stdout, 260 Stderr: ctx.Stderr, 261 AuthorizedKeys: authKeys, 262 UpdateBehavior: ¶ms.UpdateBehavior{ 263 config.EnableOSRefreshUpdate(), 264 config.EnableOSUpgrade(), 265 }, 266 } 267 machineId, err := manualProvisioner(args) 268 if err == nil { 269 ctx.Infof("created machine %v", machineId) 270 } 271 return err 272 } 273 274 logger.Infof("model provisioning") 275 if c.Placement != nil && c.Placement.Scope == "model-uuid" { 276 uuid, ok := client.ModelUUID() 277 if !ok { 278 return errors.New("API connection is controller-only (should never happen)") 279 } 280 c.Placement.Scope = uuid 281 } 282 283 if c.Placement != nil && c.Placement.Scope == instance.MachineScope { 284 // It does not make sense to add-machine <id>. 285 return errors.Errorf("machine-id cannot be specified when adding machines") 286 } 287 288 jobs := []multiwatcher.MachineJob{multiwatcher.JobHostUnits} 289 290 machineParams := params.AddMachineParams{ 291 Placement: c.Placement, 292 Series: c.Series, 293 Constraints: c.Constraints, 294 Jobs: jobs, 295 Disks: c.Disks, 296 } 297 machines := make([]params.AddMachineParams, c.NumMachines) 298 for i := 0; i < c.NumMachines; i++ { 299 machines[i] = machineParams 300 } 301 302 var results []params.AddMachinesResult 303 // If storage is specified, we attempt to use a new API on the service facade. 304 if len(c.Disks) > 0 { 305 results, err = machineManager.AddMachines(machines) 306 } else { 307 results, err = client.AddMachines(machines) 308 } 309 if params.IsCodeOperationBlocked(err) { 310 return block.ProcessBlockedError(err, block.BlockChange) 311 } 312 if err != nil { 313 return errors.Trace(err) 314 } 315 316 errs := []error{} 317 for _, machineInfo := range results { 318 if machineInfo.Error != nil { 319 errs = append(errs, machineInfo.Error) 320 continue 321 } 322 machineId := machineInfo.Machine 323 324 if names.IsContainerMachine(machineId) { 325 ctx.Infof("created container %v", machineId) 326 } else { 327 ctx.Infof("created machine %v", machineId) 328 } 329 } 330 if len(errs) == 1 { 331 fmt.Fprint(ctx.Stderr, "failed to create 1 machine\n") 332 return errs[0] 333 } 334 if len(errs) > 1 { 335 fmt.Fprintf(ctx.Stderr, "failed to create %d machines\n", len(errs)) 336 returnErr := []string{} 337 for _, e := range errs { 338 returnErr = append(returnErr, e.Error()) 339 } 340 return errors.New(strings.Join(returnErr, ", ")) 341 } 342 return nil 343 }