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