launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/state/addmachine.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strconv" 9 10 "labix.org/v2/mgo/txn" 11 12 errgo "launchpad.net/errgo/errors" 13 "launchpad.net/juju-core/constraints" 14 "launchpad.net/juju-core/errors" 15 "launchpad.net/juju-core/instance" 16 "launchpad.net/juju-core/replicaset" 17 "launchpad.net/juju-core/state/api/params" 18 "launchpad.net/juju-core/utils" 19 ) 20 21 // MachineTemplate holds attributes that are to be associated 22 // with a newly created machine. 23 type MachineTemplate struct { 24 // Series is the series to be associated with the new machine. 25 Series string 26 27 // Constraints are the constraints to be used when finding 28 // an instance for the machine. 29 Constraints constraints.Value 30 31 // Jobs holds the jobs to run on the machine's instance. 32 // A machine must have at least one job to do. 33 // JobManageEnviron can only be part of the jobs 34 // when the first (bootstrap) machine is added. 35 Jobs []MachineJob 36 37 // NoVote holds whether a machine running 38 // a state server should abstain from peer voting. 39 // It is ignored if Jobs does not contain JobManageEnviron. 40 NoVote bool 41 42 // Addresses holds the addresses to be associated with the 43 // new machine. 44 Addresses []instance.Address 45 46 // InstanceId holds the instance id to associate with the machine. 47 // If this is empty, the provisioner will try to provision the machine. 48 // If this is non-empty, the HardwareCharacteristics and Nonce 49 // fields must be set appropriately. 50 InstanceId instance.Id 51 52 // HardwareCharacteristics holds the h/w characteristics to 53 // be associated with the machine. 54 HardwareCharacteristics instance.HardwareCharacteristics 55 56 // Nonce holds a unique value that can be used to check 57 // if a new instance was really started for this machine. 58 // See Machine.SetProvisioned. This must be set if InstanceId is set. 59 Nonce string 60 61 // Dirty signifies whether the new machine will be treated 62 // as unclean for unit-assignment purposes. 63 Dirty bool 64 65 // principals holds the principal units that will 66 // associated with the machine. 67 principals []string 68 } 69 70 // AddMachineInsideNewMachine creates a new machine within a container 71 // of the given type inside another new machine. The two given templates 72 // specify the form of the child and parent respectively. 73 func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) { 74 mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType) 75 if err != nil { 76 return nil, errgo.Notef(err, "cannot add a new machine") 77 } 78 return st.addMachine(mdoc, ops) 79 } 80 81 // AddMachineInsideMachine adds a machine inside a container of the 82 // given type on the existing machine with id=parentId. 83 func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) { 84 mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType) 85 if err != nil { 86 return nil, errgo.Notef(err, "cannot add a new machine") 87 } 88 return st.addMachine(mdoc, ops) 89 } 90 91 // AddMachine adds a machine with the given series and jobs. 92 // It is deprecated and around for testing purposes only. 93 func (st *State) AddMachine(series string, jobs ...MachineJob) (*Machine, error) { 94 ms, err := st.AddMachines(MachineTemplate{ 95 Series: series, 96 Jobs: jobs, 97 }) 98 if err != nil { 99 return nil, mask(err) 100 } 101 return ms[0], nil 102 } 103 104 // AddOne machine adds a new machine configured according to the 105 // given template. 106 func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) { 107 ms, err := st.AddMachines(template) 108 if err != nil { 109 return nil, mask(err) 110 } 111 return ms[0], nil 112 } 113 114 // AddMachines adds new machines configured according to the 115 // given templates. 116 func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) { 117 defer utils.ErrorContextf(&err, "cannot add a new machine") 118 var ms []*Machine 119 env, err := st.Environment() 120 if err != nil { 121 return nil, mask(err) 122 } else if env.Life() != Alive { 123 return nil, errgo.Newf("environment is no longer alive") 124 } 125 var ops []txn.Op 126 var mdocs []*machineDoc 127 for _, template := range templates { 128 mdoc, addOps, err := st.addMachineOps(template) 129 if err != nil { 130 return nil, mask(err) 131 } 132 mdocs = append(mdocs, mdoc) 133 ms = append(ms, newMachine(st, mdoc)) 134 ops = append(ops, addOps...) 135 } 136 ssOps, err := st.maintainStateServersOps(mdocs, nil) 137 if err != nil { 138 return nil, mask(err) 139 } 140 ops = append(ops, ssOps...) 141 ops = append(ops, env.assertAliveOp()) 142 if err := st.runTransaction(ops); err != nil { 143 return nil, onAbort(err, errgo.Newf("environment is no longer alive")) 144 } 145 return ms, nil 146 } 147 148 func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) { 149 env, err := st.Environment() 150 if err != nil { 151 return nil, mask(err) 152 } else if env.Life() != Alive { 153 return nil, errgo.Newf("environment is no longer alive") 154 } 155 ops = append([]txn.Op{env.assertAliveOp()}, ops...) 156 if err := st.runTransaction(ops); err != nil { 157 enverr := env.Refresh() 158 if (enverr == nil && env.Life() != Alive) || errors.IsNotFoundError(enverr) { 159 return nil, errgo.Newf("environment is no longer alive") 160 } else if enverr != nil { 161 err = enverr 162 } 163 return nil, err 164 } 165 return newMachine(st, mdoc), nil 166 } 167 168 // effectiveMachineTemplate verifies that the given template is 169 // valid and combines it with values from the state 170 // to produce a resulting template that more accurately 171 // represents the data that will be inserted into the state. 172 func (st *State) effectiveMachineTemplate(p MachineTemplate, allowStateServer bool) (MachineTemplate, error) { 173 if p.Series == "" { 174 return MachineTemplate{}, errgo.Newf("no series specified") 175 } 176 cons, err := st.EnvironConstraints() 177 if err != nil { 178 return MachineTemplate{}, mask(err) 179 } 180 p.Constraints = p.Constraints.WithFallbacks(cons) 181 // Machine constraints do not use a container constraint value. 182 // Both provisioning and deployment constraints use the same 183 // constraints.Value struct so here we clear the container 184 // value. Provisioning ignores the container value but clearing 185 // it avoids potential confusion. 186 p.Constraints.Container = nil 187 188 if len(p.Jobs) == 0 { 189 return MachineTemplate{}, errgo.Newf("no jobs specified") 190 } 191 jset := make(map[MachineJob]bool) 192 for _, j := range p.Jobs { 193 if jset[j] { 194 return MachineTemplate{}, errgo.Newf("duplicate job: %s", j) 195 } 196 jset[j] = true 197 } 198 if jset[JobManageEnviron] { 199 if !allowStateServer { 200 return MachineTemplate{}, errStateServerNotAllowed 201 } 202 } 203 204 if p.InstanceId != "" { 205 if p.Nonce == "" { 206 return MachineTemplate{}, errgo.Newf("cannot add a machine with an instance id and no nonce") 207 } 208 } else if p.Nonce != "" { 209 return MachineTemplate{}, errgo.Newf("cannot specify a nonce without an instance id") 210 } 211 return p, nil 212 } 213 214 // addMachineOps returns operations to add a new top level machine 215 // based on the given template. It also returns the machine document 216 // that will be inserted. 217 func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) { 218 template, err := st.effectiveMachineTemplate(template, true) 219 if err != nil { 220 return nil, nil, mask(err) 221 } 222 seq, err := st.sequence("machine") 223 if err != nil { 224 return nil, nil, mask(err) 225 } 226 mdoc := machineDocForTemplate(template, strconv.Itoa(seq)) 227 var ops []txn.Op 228 ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...) 229 ops = append(ops, st.insertNewContainerRefOp(mdoc.Id)) 230 if template.InstanceId != "" { 231 ops = append(ops, txn.Op{ 232 C: st.instanceData.Name, 233 Id: mdoc.Id, 234 Assert: txn.DocMissing, 235 Insert: &instanceData{ 236 Id: mdoc.Id, 237 InstanceId: template.InstanceId, 238 Arch: template.HardwareCharacteristics.Arch, 239 Mem: template.HardwareCharacteristics.Mem, 240 RootDisk: template.HardwareCharacteristics.RootDisk, 241 CpuCores: template.HardwareCharacteristics.CpuCores, 242 CpuPower: template.HardwareCharacteristics.CpuPower, 243 Tags: template.HardwareCharacteristics.Tags, 244 }, 245 }) 246 } 247 return mdoc, ops, nil 248 } 249 250 // supportsContainerType reports whether the machine supports the given 251 // container type. If the machine's supportedContainers attribute is 252 // set, this decision can be made right here, otherwise we assume that 253 // everything will be ok and later on put the container into an error 254 // state if necessary. 255 func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool { 256 supportedContainers, ok := m.SupportedContainers() 257 if !ok { 258 // We don't know yet, so we report that we support the container. 259 return true 260 } 261 for _, ct := range supportedContainers { 262 if ct == ctype { 263 return true 264 } 265 } 266 return false 267 } 268 269 // addMachineInsideMachineOps returns operations to add a machine inside 270 // a container of the given type on an existing machine. 271 func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 272 if template.InstanceId != "" { 273 return nil, nil, errgo.Newf("cannot specify instance id for a new container") 274 } 275 template, err := st.effectiveMachineTemplate(template, false) 276 if err != nil { 277 return nil, nil, mask(err) 278 } 279 if containerType == "" { 280 return nil, nil, errgo.Newf("no container type specified") 281 } 282 283 // If a parent machine is specified, make sure it exists 284 // and can support the requested container type. 285 parent, err := st.Machine(parentId) 286 if err != nil { 287 return nil, nil, mask(err) 288 } 289 if !parent.supportsContainerType(containerType) { 290 return nil, nil, errgo.Newf("machine %s cannot host %s containers", parentId, containerType) 291 } 292 newId, err := st.newContainerId(parentId, containerType) 293 if err != nil { 294 return nil, nil, mask(err) 295 } 296 mdoc := machineDocForTemplate(template, newId) 297 mdoc.ContainerType = string(containerType) 298 var ops []txn.Op 299 ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...) 300 ops = append(ops, 301 // Update containers record for host machine. 302 st.addChildToContainerRefOp(parentId, mdoc.Id), 303 // Create a containers reference document for the container itself. 304 st.insertNewContainerRefOp(mdoc.Id), 305 ) 306 return mdoc, ops, nil 307 } 308 309 // newContainerId returns a new id for a machine within the machine 310 // with id parentId and the given container type. 311 func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) { 312 seq, err := st.sequence(fmt.Sprintf("machine%s%sContainer", parentId, containerType)) 313 if err != nil { 314 return "", mask(err) 315 } 316 return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil 317 } 318 319 // addMachineInsideNewMachineOps returns operations to create a new 320 // machine within a container of the given type inside another 321 // new machine. The two given templates specify the form 322 // of the child and parent respectively. 323 func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 324 if template.InstanceId != "" || parentTemplate.InstanceId != "" { 325 return nil, nil, errgo.Newf("cannot specify instance id for a new container") 326 } 327 seq, err := st.sequence("machine") 328 if err != nil { 329 return nil, nil, mask(err) 330 } 331 parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false) 332 if err != nil { 333 return nil, nil, mask(err) 334 } 335 parentDoc := machineDocForTemplate(parentTemplate, strconv.Itoa(seq)) 336 newId, err := st.newContainerId(parentDoc.Id, containerType) 337 if err != nil { 338 return nil, nil, mask(err) 339 } 340 template, err = st.effectiveMachineTemplate(template, false) 341 if err != nil { 342 return nil, nil, mask(err) 343 } 344 mdoc := machineDocForTemplate(template, newId) 345 mdoc.ContainerType = string(containerType) 346 var ops []txn.Op 347 ops = append(ops, st.insertNewMachineOps(parentDoc, parentTemplate.Constraints)...) 348 ops = append(ops, st.insertNewMachineOps(mdoc, template.Constraints)...) 349 ops = append(ops, 350 // The host machine doesn't exist yet, create a new containers record. 351 st.insertNewContainerRefOp(mdoc.Id), 352 // Create a containers reference document for the container itself. 353 st.insertNewContainerRefOp(parentDoc.Id, mdoc.Id), 354 ) 355 return mdoc, ops, nil 356 } 357 358 func machineDocForTemplate(template MachineTemplate, id string) *machineDoc { 359 return &machineDoc{ 360 Id: id, 361 Series: template.Series, 362 Jobs: template.Jobs, 363 Clean: !template.Dirty, 364 Principals: template.principals, 365 Life: Alive, 366 InstanceId: template.InstanceId, 367 Nonce: template.Nonce, 368 Addresses: instanceAddressesToAddresses(template.Addresses), 369 NoVote: template.NoVote, 370 } 371 } 372 373 // insertNewMachineOps returns operations to insert the given machine 374 // document and its associated constraints into the database. 375 func (st *State) insertNewMachineOps(mdoc *machineDoc, cons constraints.Value) []txn.Op { 376 return []txn.Op{ 377 { 378 C: st.machines.Name, 379 Id: mdoc.Id, 380 Assert: txn.DocMissing, 381 Insert: mdoc, 382 }, 383 createConstraintsOp(st, machineGlobalKey(mdoc.Id), cons), 384 createStatusOp(st, machineGlobalKey(mdoc.Id), statusDoc{ 385 Status: params.StatusPending, 386 }), 387 } 388 } 389 390 func hasJob(jobs []MachineJob, job MachineJob) bool { 391 for _, j := range jobs { 392 if j == job { 393 return true 394 } 395 } 396 return false 397 } 398 399 var errStateServerNotAllowed = errgo.Newf("state server jobs specified without calling EnsureAvailability") 400 401 // maintainStateServersOps returns a set of operations that will maintain 402 // the state server information when the given machine documents 403 // are added to the machines collection. If currentInfo is nil, 404 // there can be only one machine document and it must have 405 // id 0 (this is a special case to allow adding the bootstrap machine) 406 func (st *State) maintainStateServersOps(mdocs []*machineDoc, currentInfo *StateServerInfo) ([]txn.Op, error) { 407 var newIds, newVotingIds []string 408 for _, doc := range mdocs { 409 if !hasJob(doc.Jobs, JobManageEnviron) { 410 continue 411 } 412 newIds = append(newIds, doc.Id) 413 if !doc.NoVote { 414 newVotingIds = append(newVotingIds, doc.Id) 415 } 416 } 417 if len(newIds) == 0 { 418 return nil, nil 419 } 420 if currentInfo == nil { 421 // Allow bootstrap machine only. 422 if len(mdocs) != 1 || mdocs[0].Id != "0" { 423 return nil, errStateServerNotAllowed 424 } 425 var err error 426 currentInfo, err = st.StateServerInfo() 427 if err != nil { 428 return nil, errgo.Notef(err, "cannot get state server info") 429 } 430 if len(currentInfo.MachineIds) > 0 || len(currentInfo.VotingMachineIds) > 0 { 431 return nil, errgo.Newf("state servers already exist") 432 } 433 } 434 ops := []txn.Op{{ 435 C: st.stateServers.Name, 436 Id: environGlobalKey, 437 Assert: D{{ 438 "$and", []D{ 439 {{"machineids", D{{"$size", len(currentInfo.MachineIds)}}}}, 440 {{"votingmachineids", D{{"$size", len(currentInfo.VotingMachineIds)}}}}, 441 }, 442 }}, 443 Update: D{ 444 {"$addToSet", D{{"machineids", D{{"$each", newIds}}}}}, 445 {"$addToSet", D{{"votingmachineids", D{{"$each", newVotingIds}}}}}, 446 }, 447 }} 448 return ops, nil 449 } 450 451 // EnsureAvailability adds state server machines as necessary to make 452 // the number of live state servers equal to numStateServers. The given 453 // constraints and series will be attached to any new machines. 454 // 455 // TODO(rog): 456 // If any current state servers are down, they will be 457 // removed from the current set of voting replica set 458 // peers (although the machines themselves will remain 459 // and they will still remain part of the replica set). 460 // Once a machine's voting status has been removed, 461 // the machine itself may be removed. 462 func (st *State) EnsureAvailability(numStateServers int, cons constraints.Value, series string) error { 463 if numStateServers%2 != 1 || numStateServers <= 0 { 464 return errgo.Newf("number of state servers must be odd and greater than zero") 465 } 466 if numStateServers > replicaset.MaxPeers { 467 return errgo.Newf("state server count is too large (allowed %d)", replicaset.MaxPeers) 468 } 469 info, err := st.StateServerInfo() 470 if err != nil { 471 return mask(err) 472 } 473 if len(info.VotingMachineIds) == numStateServers { 474 // TODO(rog) #1271504 2014-01-22 475 // Find machines which are down, set 476 // their NoVote flag and add new machines to 477 // replace them. 478 return nil 479 } 480 if len(info.VotingMachineIds) > numStateServers { 481 return errgo.Newf("cannot reduce state server count") 482 } 483 mdocs := make([]*machineDoc, 0, numStateServers-len(info.MachineIds)) 484 var ops []txn.Op 485 for i := len(info.MachineIds); i < numStateServers; i++ { 486 template := MachineTemplate{ 487 Series: series, 488 Jobs: []MachineJob{ 489 JobHostUnits, 490 JobManageEnviron, 491 }, 492 Constraints: cons, 493 } 494 mdoc, addOps, err := st.addMachineOps(template) 495 if err != nil { 496 return mask(err) 497 } 498 mdocs = append(mdocs, mdoc) 499 ops = append(ops, addOps...) 500 } 501 ssOps, err := st.maintainStateServersOps(mdocs, info) 502 if err != nil { 503 return errgo.Notef(err, "cannot prepare machine add operations") 504 } 505 ops = append(ops, ssOps...) 506 err = st.runTransaction(ops) 507 if err != nil { 508 return errgo.Notef(err, "failed to create new state server machines") 509 } 510 return nil 511 }