github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/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" as an argument; 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. 63 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 (start a machine in zone us-east-1a on AWS) 74 juju machine add maas2.name (acquire machine maas2.name on MAAS) 75 76 See Also: 77 juju help constraints 78 juju help placement 79 ` 80 81 func init() { 82 containerTypes := make([]string, len(instance.ContainerTypes)) 83 for i, t := range instance.ContainerTypes { 84 containerTypes[i] = string(t) 85 } 86 addMachineDoc = strings.Replace( 87 addMachineDoc, 88 "$CONTAINER_TYPES$", 89 strings.Join(containerTypes, ", "), 90 -1, 91 ) 92 } 93 94 // AddCommand starts a new machine and registers it in the environment. 95 type AddCommand struct { 96 envcmd.EnvCommandBase 97 api AddMachineAPI 98 machineManagerAPI MachineManagerAPI 99 // If specified, use this series, else use the environment default-series 100 Series string 101 // If specified, these constraints are merged with those already in the environment. 102 Constraints constraints.Value 103 // Placement is passed verbatim to the API, to be parsed and evaluated server-side. 104 Placement *instance.Placement 105 // NumMachines is the number of machines to add. 106 NumMachines int 107 // Disks describes disks that are to be attached to the machine. 108 Disks []storage.Constraints 109 } 110 111 func (c *AddCommand) Info() *cmd.Info { 112 return &cmd.Info{ 113 Name: "add", 114 Args: "[<container>:machine | <container> | ssh:[user@]host | placement]", 115 Purpose: "start a new, empty machine and optionally a container, or add a container to a machine", 116 Doc: addMachineDoc, 117 } 118 } 119 120 func (c *AddCommand) SetFlags(f *gnuflag.FlagSet) { 121 f.StringVar(&c.Series, "series", "", "the charm series") 122 f.IntVar(&c.NumMachines, "n", 1, "The number of machines to add") 123 f.Var(constraints.ConstraintsValue{Target: &c.Constraints}, "constraints", "additional machine constraints") 124 f.Var(disksFlag{&c.Disks}, "disks", "constraints for disks to attach to the machine") 125 } 126 127 func (c *AddCommand) Init(args []string) error { 128 if c.Constraints.Container != nil { 129 return fmt.Errorf("container constraint %q not allowed when adding a machine", *c.Constraints.Container) 130 } 131 placement, err := cmd.ZeroOrOneArgs(args) 132 if err != nil { 133 return err 134 } 135 c.Placement, err = instance.ParsePlacement(placement) 136 if err == instance.ErrPlacementScopeMissing { 137 placement = "env-uuid" + ":" + placement 138 c.Placement, err = instance.ParsePlacement(placement) 139 } 140 if err != nil { 141 return err 142 } 143 if c.NumMachines > 1 && c.Placement != nil && c.Placement.Directive != "" { 144 return fmt.Errorf("cannot use -n when specifying a placement directive") 145 } 146 return nil 147 } 148 149 type AddMachineAPI interface { 150 AddMachines([]params.AddMachineParams) ([]params.AddMachinesResult, error) 151 AddMachines1dot18([]params.AddMachineParams) ([]params.AddMachinesResult, error) 152 Close() error 153 ForceDestroyMachines(machines ...string) error 154 EnvironmentGet() (map[string]interface{}, error) 155 EnvironmentUUID() string 156 ProvisioningScript(params.ProvisioningScriptParams) (script string, err error) 157 } 158 159 type MachineManagerAPI interface { 160 AddMachines([]params.AddMachineParams) ([]params.AddMachinesResult, error) 161 BestAPIVersion() int 162 Close() error 163 } 164 165 var manualProvisioner = manual.ProvisionMachine 166 167 func (c *AddCommand) getClientAPI() (AddMachineAPI, error) { 168 if c.api != nil { 169 return c.api, nil 170 } 171 return c.NewAPIClient() 172 } 173 174 func (c *AddCommand) NewMachineManagerClient() (*machinemanager.Client, error) { 175 root, err := c.NewAPIRoot() 176 if err != nil { 177 return nil, errors.Trace(err) 178 } 179 return machinemanager.NewClient(root), nil 180 } 181 182 func (c *AddCommand) getMachineManagerAPI() (MachineManagerAPI, error) { 183 if c.machineManagerAPI != nil { 184 return c.machineManagerAPI, nil 185 } 186 return c.NewMachineManagerClient() 187 } 188 189 func (c *AddCommand) Run(ctx *cmd.Context) error { 190 client, err := c.getClientAPI() 191 if err != nil { 192 return errors.Trace(err) 193 } 194 defer client.Close() 195 196 var machineManager MachineManagerAPI 197 if len(c.Disks) > 0 { 198 machineManager, err = c.getMachineManagerAPI() 199 if err != nil { 200 return errors.Trace(err) 201 } 202 defer machineManager.Close() 203 if machineManager.BestAPIVersion() < 1 { 204 return errors.New("cannot add machines with disks: not supported by the API server") 205 } 206 } 207 208 logger.Infof("load config") 209 var config *config.Config 210 if defaultStore, err := configstore.Default(); err != nil { 211 return err 212 } else if config, err = c.Config(defaultStore, client); err != nil { 213 return err 214 } 215 216 if c.Placement != nil && c.Placement.Scope == "ssh" { 217 logger.Infof("manual provisioning") 218 args := manual.ProvisionMachineArgs{ 219 Host: c.Placement.Directive, 220 Client: client, 221 Stdin: ctx.Stdin, 222 Stdout: ctx.Stdout, 223 Stderr: ctx.Stderr, 224 UpdateBehavior: ¶ms.UpdateBehavior{ 225 config.EnableOSRefreshUpdate(), 226 config.EnableOSUpgrade(), 227 }, 228 } 229 machineId, err := manualProvisioner(args) 230 if err == nil { 231 ctx.Infof("created machine %v", machineId) 232 } 233 return err 234 } 235 236 logger.Infof("environment provisioning") 237 if c.Placement != nil && c.Placement.Scope == "env-uuid" { 238 c.Placement.Scope = client.EnvironmentUUID() 239 } 240 241 if c.Placement != nil && c.Placement.Scope == instance.MachineScope { 242 // It does not make sense to add-machine <id>. 243 return fmt.Errorf("machine-id cannot be specified when adding machines") 244 } 245 246 jobs := []multiwatcher.MachineJob{multiwatcher.JobHostUnits} 247 248 envVersion, err := envcmd.GetEnvironmentVersion(client) 249 if err != nil { 250 return err 251 } 252 253 // Servers before 1.21-alpha2 don't have the networker so don't 254 // try to use JobManageNetworking with them. 255 // 256 // In case of MAAS and Joyent JobManageNetworking is not added 257 // to ensure the non-intrusive start of a networker like above 258 // for the manual provisioning. See this related joyent bug 259 // http://pad.lv/1401423 260 if envVersion.Compare(version.MustParse("1.21-alpha2")) >= 0 && 261 config.Type() != provider.MAAS && 262 config.Type() != provider.Joyent { 263 jobs = append(jobs, multiwatcher.JobManageNetworking) 264 } 265 266 machineParams := params.AddMachineParams{ 267 Placement: c.Placement, 268 Series: c.Series, 269 Constraints: c.Constraints, 270 Jobs: jobs, 271 Disks: c.Disks, 272 } 273 machines := make([]params.AddMachineParams, c.NumMachines) 274 for i := 0; i < c.NumMachines; i++ { 275 machines[i] = machineParams 276 } 277 278 var results []params.AddMachinesResult 279 // If storage is specified, we attempt to use a new API on the service facade. 280 if len(c.Disks) > 0 { 281 results, err = machineManager.AddMachines(machines) 282 } else { 283 results, err = client.AddMachines(machines) 284 if params.IsCodeNotImplemented(err) { 285 if c.Placement != nil { 286 containerType, parseErr := instance.ParseContainerType(c.Placement.Scope) 287 if parseErr != nil { 288 // The user specified a non-container placement directive: 289 // return original API not implemented error. 290 return err 291 } 292 machineParams.ContainerType = containerType 293 machineParams.ParentId = c.Placement.Directive 294 machineParams.Placement = nil 295 } 296 logger.Infof( 297 "AddMachinesWithPlacement not supported by the API server, " + 298 "falling back to 1.18 compatibility mode", 299 ) 300 results, err = client.AddMachines1dot18([]params.AddMachineParams{machineParams}) 301 } 302 } 303 if params.IsCodeOperationBlocked(err) { 304 return block.ProcessBlockedError(err, block.BlockChange) 305 } 306 if err != nil { 307 return errors.Trace(err) 308 } 309 310 errs := []error{} 311 for _, machineInfo := range results { 312 if machineInfo.Error != nil { 313 errs = append(errs, machineInfo.Error) 314 continue 315 } 316 machineId := machineInfo.Machine 317 318 if names.IsContainerMachine(machineId) { 319 ctx.Infof("created container %v", machineId) 320 } else { 321 ctx.Infof("created machine %v", machineId) 322 } 323 } 324 if len(errs) == 1 { 325 fmt.Fprintf(ctx.Stderr, "failed to create 1 machine\n") 326 return errs[0] 327 } 328 if len(errs) > 1 { 329 fmt.Fprintf(ctx.Stderr, "failed to create %d machines\n", len(errs)) 330 returnErr := []string{} 331 for _, e := range errs { 332 returnErr = append(returnErr, e.Error()) 333 } 334 return errors.New(strings.Join(returnErr, ", ")) 335 } 336 return nil 337 }