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