github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/names" 13 "github.com/juju/replicaset" 14 jujutxn "github.com/juju/txn" 15 "gopkg.in/mgo.v2/bson" 16 "gopkg.in/mgo.v2/txn" 17 18 "github.com/juju/juju/constraints" 19 "github.com/juju/juju/instance" 20 "github.com/juju/juju/network" 21 "github.com/juju/juju/status" 22 ) 23 24 // MachineTemplate holds attributes that are to be associated 25 // with a newly created machine. 26 type MachineTemplate struct { 27 // Series is the series to be associated with the new machine. 28 Series string 29 30 // Constraints are the constraints to be used when finding 31 // an instance for the machine. 32 Constraints constraints.Value 33 34 // Jobs holds the jobs to run on the machine's instance. 35 // A machine must have at least one job to do. 36 // JobManageModel can only be part of the jobs 37 // when the first (bootstrap) machine is added. 38 Jobs []MachineJob 39 40 // NoVote holds whether a machine running 41 // a controller should abstain from peer voting. 42 // It is ignored if Jobs does not contain JobManageModel. 43 NoVote bool 44 45 // Addresses holds the addresses to be associated with the 46 // new machine. 47 // 48 // TODO(dimitern): This should be removed once all addresses 49 // come from link-layer device addresses. 50 Addresses []network.Address 51 52 // InstanceId holds the instance id to associate with the machine. 53 // If this is empty, the provisioner will try to provision the machine. 54 // If this is non-empty, the HardwareCharacteristics and Nonce 55 // fields must be set appropriately. 56 InstanceId instance.Id 57 58 // HardwareCharacteristics holds the h/w characteristics to 59 // be associated with the machine. 60 HardwareCharacteristics instance.HardwareCharacteristics 61 62 // LinkLayerDevices holds a list of arguments for setting link-layer devices 63 // on the machine. 64 LinkLayerDevices []LinkLayerDeviceArgs 65 66 // Volumes holds the parameters for volumes that are to be created 67 // and attached to the machine. 68 Volumes []MachineVolumeParams 69 70 // VolumeAttachments holds the parameters for attaching existing 71 // volumes to the machine. 72 VolumeAttachments map[names.VolumeTag]VolumeAttachmentParams 73 74 // Filesystems holds the parameters for filesystems that are to be 75 // created and attached to the machine. 76 Filesystems []MachineFilesystemParams 77 78 // FilesystemAttachments holds the parameters for attaching existing 79 // filesystems to the machine. 80 FilesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams 81 82 // Nonce holds a unique value that can be used to check 83 // if a new instance was really started for this machine. 84 // See Machine.SetProvisioned. This must be set if InstanceId is set. 85 Nonce string 86 87 // Dirty signifies whether the new machine will be treated 88 // as unclean for unit-assignment purposes. 89 Dirty bool 90 91 // Placement holds the placement directive that will be associated 92 // with the machine. 93 Placement string 94 95 // principals holds the principal units that will 96 // associated with the machine. 97 principals []string 98 } 99 100 // MachineVolumeParams holds the parameters for creating a volume and 101 // attaching it to a new machine. 102 type MachineVolumeParams struct { 103 Volume VolumeParams 104 Attachment VolumeAttachmentParams 105 } 106 107 // MachineFilesystemParams holds the parameters for creating a filesystem 108 // and attaching it to a new machine. 109 type MachineFilesystemParams struct { 110 Filesystem FilesystemParams 111 Attachment FilesystemAttachmentParams 112 } 113 114 // AddMachineInsideNewMachine creates a new machine within a container 115 // of the given type inside another new machine. The two given templates 116 // specify the form of the child and parent respectively. 117 func (st *State) AddMachineInsideNewMachine(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*Machine, error) { 118 mdoc, ops, err := st.addMachineInsideNewMachineOps(template, parentTemplate, containerType) 119 if err != nil { 120 return nil, errors.Annotate(err, "cannot add a new machine") 121 } 122 return st.addMachine(mdoc, ops) 123 } 124 125 // AddMachineInsideMachine adds a machine inside a container of the 126 // given type on the existing machine with id=parentId. 127 func (st *State) AddMachineInsideMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) (*Machine, error) { 128 mdoc, ops, err := st.addMachineInsideMachineOps(template, parentId, containerType) 129 if err != nil { 130 return nil, errors.Annotate(err, "cannot add a new machine") 131 } 132 return st.addMachine(mdoc, ops) 133 } 134 135 // AddMachine adds a machine with the given series and jobs. 136 // It is deprecated and around for testing purposes only. 137 func (st *State) AddMachine(series string, jobs ...MachineJob) (*Machine, error) { 138 ms, err := st.AddMachines(MachineTemplate{ 139 Series: series, 140 Jobs: jobs, 141 }) 142 if err != nil { 143 return nil, err 144 } 145 return ms[0], nil 146 } 147 148 // AddOneMachine machine adds a new machine configured according to the 149 // given template. 150 func (st *State) AddOneMachine(template MachineTemplate) (*Machine, error) { 151 ms, err := st.AddMachines(template) 152 if err != nil { 153 return nil, err 154 } 155 return ms[0], nil 156 } 157 158 // AddMachines adds new machines configured according to the 159 // given templates. 160 func (st *State) AddMachines(templates ...MachineTemplate) (_ []*Machine, err error) { 161 defer errors.DeferredAnnotatef(&err, "cannot add a new machine") 162 var ms []*Machine 163 var ops []txn.Op 164 var mdocs []*machineDoc 165 for _, template := range templates { 166 // Adding a machine without any principals is 167 // only permitted if unit placement is supported. 168 if len(template.principals) == 0 && template.InstanceId == "" { 169 if err := st.supportsUnitPlacement(); err != nil { 170 return nil, errors.Trace(err) 171 } 172 } 173 mdoc, addOps, err := st.addMachineOps(template) 174 if err != nil { 175 return nil, errors.Trace(err) 176 } 177 mdocs = append(mdocs, mdoc) 178 ms = append(ms, newMachine(st, mdoc)) 179 ops = append(ops, addOps...) 180 } 181 ssOps, err := st.maintainControllersOps(mdocs, nil) 182 if err != nil { 183 return nil, errors.Trace(err) 184 } 185 ops = append(ops, ssOps...) 186 ops = append(ops, assertModelActiveOp(st.ModelUUID())) 187 if err := st.runTransaction(ops); err != nil { 188 if errors.Cause(err) == txn.ErrAborted { 189 if err := checkModelActive(st); err != nil { 190 return nil, errors.Trace(err) 191 } 192 } 193 return nil, errors.Trace(err) 194 } 195 return ms, nil 196 } 197 198 func (st *State) addMachine(mdoc *machineDoc, ops []txn.Op) (*Machine, error) { 199 ops = append([]txn.Op{assertModelActiveOp(st.ModelUUID())}, ops...) 200 if err := st.runTransaction(ops); err != nil { 201 if errors.Cause(err) == txn.ErrAborted { 202 if err := checkModelActive(st); err != nil { 203 return nil, errors.Trace(err) 204 } 205 } 206 return nil, errors.Trace(err) 207 } 208 return newMachine(st, mdoc), nil 209 } 210 211 func (st *State) resolveMachineConstraints(cons constraints.Value) (constraints.Value, error) { 212 mcons, err := st.resolveConstraints(cons) 213 if err != nil { 214 return constraints.Value{}, err 215 } 216 // Machine constraints do not use a container constraint value. 217 // Both provisioning and deployment constraints use the same 218 // constraints.Value struct so here we clear the container 219 // value. Provisioning ignores the container value but clearing 220 // it avoids potential confusion. 221 mcons.Container = nil 222 return mcons, nil 223 } 224 225 // effectiveMachineTemplate verifies that the given template is 226 // valid and combines it with values from the state 227 // to produce a resulting template that more accurately 228 // represents the data that will be inserted into the state. 229 func (st *State) effectiveMachineTemplate(p MachineTemplate, allowController bool) (tmpl MachineTemplate, err error) { 230 // First check for obvious errors. 231 if p.Series == "" { 232 return tmpl, errors.New("no series specified") 233 } 234 if p.InstanceId != "" { 235 if p.Nonce == "" { 236 return tmpl, errors.New("cannot add a machine with an instance id and no nonce") 237 } 238 } else if p.Nonce != "" { 239 return tmpl, errors.New("cannot specify a nonce without an instance id") 240 } 241 242 p.Constraints, err = st.resolveMachineConstraints(p.Constraints) 243 if err != nil { 244 return tmpl, err 245 } 246 247 if len(p.Jobs) == 0 { 248 return tmpl, errors.New("no jobs specified") 249 } 250 jset := make(map[MachineJob]bool) 251 for _, j := range p.Jobs { 252 if jset[j] { 253 return MachineTemplate{}, errors.Errorf("duplicate job: %s", j) 254 } 255 jset[j] = true 256 } 257 if jset[JobManageModel] { 258 if !allowController { 259 return tmpl, errControllerNotAllowed 260 } 261 } 262 return p, nil 263 } 264 265 // addMachineOps returns operations to add a new top level machine 266 // based on the given template. It also returns the machine document 267 // that will be inserted. 268 func (st *State) addMachineOps(template MachineTemplate) (*machineDoc, []txn.Op, error) { 269 template, err := st.effectiveMachineTemplate(template, st.IsController()) 270 if err != nil { 271 return nil, nil, err 272 } 273 if template.InstanceId == "" { 274 if err := st.precheckInstance(template.Series, template.Constraints, template.Placement); err != nil { 275 return nil, nil, err 276 } 277 } 278 seq, err := st.sequence("machine") 279 if err != nil { 280 return nil, nil, err 281 } 282 mdoc := st.machineDocForTemplate(template, strconv.Itoa(seq)) 283 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 284 if err != nil { 285 return nil, nil, errors.Trace(err) 286 } 287 prereqOps = append(prereqOps, assertModelActiveOp(st.ModelUUID())) 288 prereqOps = append(prereqOps, st.insertNewContainerRefOp(mdoc.Id)) 289 if template.InstanceId != "" { 290 prereqOps = append(prereqOps, txn.Op{ 291 C: instanceDataC, 292 Id: mdoc.DocID, 293 Assert: txn.DocMissing, 294 Insert: &instanceData{ 295 DocID: mdoc.DocID, 296 MachineId: mdoc.Id, 297 InstanceId: template.InstanceId, 298 ModelUUID: mdoc.ModelUUID, 299 Arch: template.HardwareCharacteristics.Arch, 300 Mem: template.HardwareCharacteristics.Mem, 301 RootDisk: template.HardwareCharacteristics.RootDisk, 302 CpuCores: template.HardwareCharacteristics.CpuCores, 303 CpuPower: template.HardwareCharacteristics.CpuPower, 304 Tags: template.HardwareCharacteristics.Tags, 305 AvailZone: template.HardwareCharacteristics.AvailabilityZone, 306 }, 307 }) 308 } 309 310 return mdoc, append(prereqOps, machineOp), nil 311 } 312 313 // supportsContainerType reports whether the machine supports the given 314 // container type. If the machine's supportedContainers attribute is 315 // set, this decision can be made right here, otherwise we assume that 316 // everything will be ok and later on put the container into an error 317 // state if necessary. 318 func (m *Machine) supportsContainerType(ctype instance.ContainerType) bool { 319 supportedContainers, ok := m.SupportedContainers() 320 if !ok { 321 // We don't know yet, so we report that we support the container. 322 return true 323 } 324 for _, ct := range supportedContainers { 325 if ct == ctype { 326 return true 327 } 328 } 329 return false 330 } 331 332 // addMachineInsideMachineOps returns operations to add a machine inside 333 // a container of the given type on an existing machine. 334 func (st *State) addMachineInsideMachineOps(template MachineTemplate, parentId string, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 335 if template.InstanceId != "" { 336 return nil, nil, errors.New("cannot specify instance id for a new container") 337 } 338 template, err := st.effectiveMachineTemplate(template, false) 339 if err != nil { 340 return nil, nil, err 341 } 342 if containerType == "" { 343 return nil, nil, errors.New("no container type specified") 344 } 345 // Adding a machine within a machine implies add-machine or placement. 346 if err := st.supportsUnitPlacement(); err != nil { 347 return nil, nil, err 348 } 349 350 // If a parent machine is specified, make sure it exists 351 // and can support the requested container type. 352 parent, err := st.Machine(parentId) 353 if err != nil { 354 return nil, nil, err 355 } 356 if !parent.supportsContainerType(containerType) { 357 return nil, nil, errors.Errorf("machine %s cannot host %s containers", parentId, containerType) 358 } 359 360 newId, err := st.newContainerId(parentId, containerType) 361 if err != nil { 362 return nil, nil, err 363 } 364 mdoc := st.machineDocForTemplate(template, newId) 365 mdoc.ContainerType = string(containerType) 366 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 367 if err != nil { 368 return nil, nil, errors.Trace(err) 369 } 370 prereqOps = append(prereqOps, 371 // Update containers record for host machine. 372 st.addChildToContainerRefOp(parentId, mdoc.Id), 373 // Create a containers reference document for the container itself. 374 st.insertNewContainerRefOp(mdoc.Id), 375 ) 376 return mdoc, append(prereqOps, machineOp), nil 377 } 378 379 // newContainerId returns a new id for a machine within the machine 380 // with id parentId and the given container type. 381 func (st *State) newContainerId(parentId string, containerType instance.ContainerType) (string, error) { 382 seq, err := st.sequence(fmt.Sprintf("machine%s%sContainer", parentId, containerType)) 383 if err != nil { 384 return "", err 385 } 386 return fmt.Sprintf("%s/%s/%d", parentId, containerType, seq), nil 387 } 388 389 // addMachineInsideNewMachineOps returns operations to create a new 390 // machine within a container of the given type inside another 391 // new machine. The two given templates specify the form 392 // of the child and parent respectively. 393 func (st *State) addMachineInsideNewMachineOps(template, parentTemplate MachineTemplate, containerType instance.ContainerType) (*machineDoc, []txn.Op, error) { 394 if template.InstanceId != "" || parentTemplate.InstanceId != "" { 395 return nil, nil, errors.New("cannot specify instance id for a new container") 396 } 397 seq, err := st.sequence("machine") 398 if err != nil { 399 return nil, nil, err 400 } 401 parentTemplate, err = st.effectiveMachineTemplate(parentTemplate, false) 402 if err != nil { 403 return nil, nil, err 404 } 405 if containerType == "" { 406 return nil, nil, errors.New("no container type specified") 407 } 408 if parentTemplate.InstanceId == "" { 409 // Adding a machine within a machine implies add-machine or placement. 410 if err := st.supportsUnitPlacement(); err != nil { 411 return nil, nil, err 412 } 413 if err := st.precheckInstance(parentTemplate.Series, parentTemplate.Constraints, parentTemplate.Placement); err != nil { 414 return nil, nil, err 415 } 416 } 417 418 parentDoc := st.machineDocForTemplate(parentTemplate, strconv.Itoa(seq)) 419 newId, err := st.newContainerId(parentDoc.Id, containerType) 420 if err != nil { 421 return nil, nil, err 422 } 423 template, err = st.effectiveMachineTemplate(template, false) 424 if err != nil { 425 return nil, nil, err 426 } 427 mdoc := st.machineDocForTemplate(template, newId) 428 mdoc.ContainerType = string(containerType) 429 parentPrereqOps, parentOp, err := st.insertNewMachineOps(parentDoc, parentTemplate) 430 if err != nil { 431 return nil, nil, errors.Trace(err) 432 } 433 prereqOps, machineOp, err := st.insertNewMachineOps(mdoc, template) 434 if err != nil { 435 return nil, nil, errors.Trace(err) 436 } 437 prereqOps = append(prereqOps, parentPrereqOps...) 438 prereqOps = append(prereqOps, 439 // The host machine doesn't exist yet, create a new containers record. 440 st.insertNewContainerRefOp(mdoc.Id), 441 // Create a containers reference document for the container itself. 442 st.insertNewContainerRefOp(parentDoc.Id, mdoc.Id), 443 ) 444 return mdoc, append(prereqOps, parentOp, machineOp), nil 445 } 446 447 func (st *State) machineDocForTemplate(template MachineTemplate, id string) *machineDoc { 448 // We ignore the error from Select*Address as an error indicates 449 // no address is available, in which case the empty address is returned 450 // and setting the preferred address to an empty one is the correct 451 // thing to do when none is available. 452 privateAddr, _ := network.SelectInternalAddress(template.Addresses, false) 453 publicAddr, _ := network.SelectPublicAddress(template.Addresses) 454 return &machineDoc{ 455 DocID: st.docID(id), 456 Id: id, 457 ModelUUID: st.ModelUUID(), 458 Series: template.Series, 459 Jobs: template.Jobs, 460 Clean: !template.Dirty, 461 Principals: template.principals, 462 Life: Alive, 463 Nonce: template.Nonce, 464 Addresses: fromNetworkAddresses(template.Addresses, OriginMachine), 465 PreferredPrivateAddress: fromNetworkAddress(privateAddr, OriginMachine), 466 PreferredPublicAddress: fromNetworkAddress(publicAddr, OriginMachine), 467 NoVote: template.NoVote, 468 Placement: template.Placement, 469 } 470 } 471 472 // insertNewMachineOps returns operations to insert the given machine document 473 // into the database, based on the given template. Only the constraints are 474 // taken from the template. 475 func (st *State) insertNewMachineOps(mdoc *machineDoc, template MachineTemplate) (prereqOps []txn.Op, machineOp txn.Op, err error) { 476 machineStatusDoc := statusDoc{ 477 Status: status.StatusPending, 478 ModelUUID: st.ModelUUID(), 479 // TODO(fwereade): 2016-03-17 lp:1558657 480 Updated: time.Now().UnixNano(), 481 } 482 instanceStatusDoc := statusDoc{ 483 Status: status.StatusPending, 484 ModelUUID: st.ModelUUID(), 485 Updated: time.Now().UnixNano(), 486 } 487 488 prereqOps, machineOp = st.baseNewMachineOps( 489 mdoc, 490 machineStatusDoc, 491 instanceStatusDoc, 492 template.Constraints, 493 ) 494 495 storageOps, volumeAttachments, filesystemAttachments, err := st.machineStorageOps( 496 mdoc, &machineStorageParams{ 497 filesystems: template.Filesystems, 498 filesystemAttachments: template.FilesystemAttachments, 499 volumes: template.Volumes, 500 volumeAttachments: template.VolumeAttachments, 501 }, 502 ) 503 if err != nil { 504 return nil, txn.Op{}, errors.Trace(err) 505 } 506 for _, a := range volumeAttachments { 507 mdoc.Volumes = append(mdoc.Volumes, a.tag.Id()) 508 } 509 for _, a := range filesystemAttachments { 510 mdoc.Filesystems = append(mdoc.Filesystems, a.tag.Id()) 511 } 512 prereqOps = append(prereqOps, storageOps...) 513 514 // At the last moment we still have statusDoc in scope, set the initial 515 // history entry. This is risky, and may lead to extra entries, but that's 516 // an intrinsic problem with mixing txn and non-txn ops -- we can't sync 517 // them cleanly. 518 probablyUpdateStatusHistory(st, machineGlobalKey(mdoc.Id), machineStatusDoc) 519 probablyUpdateStatusHistory(st, machineGlobalInstanceKey(mdoc.Id), instanceStatusDoc) 520 return prereqOps, machineOp, nil 521 } 522 523 func (st *State) baseNewMachineOps(mdoc *machineDoc, machineStatusDoc, instanceStatusDoc statusDoc, cons constraints.Value) (prereqOps []txn.Op, machineOp txn.Op) { 524 machineOp = txn.Op{ 525 C: machinesC, 526 Id: mdoc.DocID, 527 Assert: txn.DocMissing, 528 Insert: mdoc, 529 } 530 531 globalKey := machineGlobalKey(mdoc.Id) 532 globalInstanceKey := machineGlobalInstanceKey(mdoc.Id) 533 534 prereqOps = []txn.Op{ 535 createConstraintsOp(st, globalKey, cons), 536 createStatusOp(st, globalKey, machineStatusDoc), 537 createStatusOp(st, globalInstanceKey, instanceStatusDoc), 538 createMachineBlockDevicesOp(mdoc.Id), 539 addModelMachineRefOp(st, mdoc.Id), 540 } 541 return prereqOps, machineOp 542 } 543 544 type machineStorageParams struct { 545 volumes []MachineVolumeParams 546 volumeAttachments map[names.VolumeTag]VolumeAttachmentParams 547 filesystems []MachineFilesystemParams 548 filesystemAttachments map[names.FilesystemTag]FilesystemAttachmentParams 549 } 550 551 // machineStorageOps creates txn.Ops for creating volumes, filesystems, 552 // and attachments to the specified machine. The results are the txn.Ops, 553 // and the tags of volumes and filesystems newly attached to the machine. 554 func (st *State) machineStorageOps( 555 mdoc *machineDoc, args *machineStorageParams, 556 ) ([]txn.Op, []volumeAttachmentTemplate, []filesystemAttachmentTemplate, error) { 557 var filesystemOps, volumeOps []txn.Op 558 var fsAttachments []filesystemAttachmentTemplate 559 var volumeAttachments []volumeAttachmentTemplate 560 561 // Create filesystems and filesystem attachments. 562 for _, f := range args.filesystems { 563 ops, filesystemTag, volumeTag, err := st.addFilesystemOps(f.Filesystem, mdoc.Id) 564 if err != nil { 565 return nil, nil, nil, errors.Trace(err) 566 } 567 filesystemOps = append(filesystemOps, ops...) 568 fsAttachments = append(fsAttachments, filesystemAttachmentTemplate{ 569 filesystemTag, f.Filesystem.storage, f.Attachment, 570 }) 571 if volumeTag != (names.VolumeTag{}) { 572 // The filesystem requires a volume, so create a volume attachment too. 573 volumeAttachments = append(volumeAttachments, volumeAttachmentTemplate{ 574 volumeTag, VolumeAttachmentParams{}, 575 }) 576 } 577 } 578 579 // Create volumes and volume attachments. 580 for _, v := range args.volumes { 581 ops, tag, err := st.addVolumeOps(v.Volume, mdoc.Id) 582 if err != nil { 583 return nil, nil, nil, errors.Trace(err) 584 } 585 volumeOps = append(volumeOps, ops...) 586 volumeAttachments = append(volumeAttachments, volumeAttachmentTemplate{ 587 tag, v.Attachment, 588 }) 589 } 590 591 // TODO(axw) handle args.filesystemAttachments, args.volumeAttachments 592 // when we handle attaching to existing (e.g. shared) storage. 593 594 ops := make([]txn.Op, 0, len(filesystemOps)+len(volumeOps)+len(fsAttachments)+len(volumeAttachments)) 595 if len(fsAttachments) > 0 { 596 attachmentOps := createMachineFilesystemAttachmentsOps(mdoc.Id, fsAttachments) 597 ops = append(ops, filesystemOps...) 598 ops = append(ops, attachmentOps...) 599 } 600 if len(volumeAttachments) > 0 { 601 attachmentOps := createMachineVolumeAttachmentsOps(mdoc.Id, volumeAttachments) 602 ops = append(ops, volumeOps...) 603 ops = append(ops, attachmentOps...) 604 } 605 return ops, volumeAttachments, fsAttachments, nil 606 } 607 608 // addMachineStorageAttachmentsOps returns txn.Ops for adding the IDs of 609 // attached volumes and filesystems to an existing machine. Filesystem 610 // mount points are checked against existing filesystem attachments for 611 // conflicts, with a txn.Op added to prevent concurrent additions as 612 // necessary. 613 func addMachineStorageAttachmentsOps( 614 machine *Machine, 615 volumes []volumeAttachmentTemplate, 616 filesystems []filesystemAttachmentTemplate, 617 ) ([]txn.Op, error) { 618 var updates bson.D 619 assert := isAliveDoc 620 if len(volumes) > 0 { 621 volumeIds := make([]string, len(volumes)) 622 for i, v := range volumes { 623 volumeIds[i] = v.tag.Id() 624 } 625 updates = append(updates, bson.DocElem{"$addToSet", bson.D{{ 626 "volumes", bson.D{{"$each", volumeIds}}}}, 627 }) 628 } 629 if len(filesystems) > 0 { 630 filesystemIds := make([]string, len(filesystems)) 631 var withLocation []filesystemAttachmentTemplate 632 for i, f := range filesystems { 633 filesystemIds[i] = f.tag.Id() 634 if !f.params.locationAutoGenerated { 635 // If the location was not automatically 636 // generated, we must ensure it does not 637 // conflict with any existing storage. 638 // Generated paths are guaranteed to be 639 // unique. 640 withLocation = append(withLocation, f) 641 } 642 } 643 updates = append(updates, bson.DocElem{"$addToSet", bson.D{{ 644 "filesystems", bson.D{{"$each", filesystemIds}}}}, 645 }) 646 if len(withLocation) > 0 { 647 if err := validateFilesystemMountPoints(machine, withLocation); err != nil { 648 return nil, errors.Annotate(err, "validating filesystem mount points") 649 } 650 // Make sure no filesystems are added concurrently. 651 assert = append(assert, bson.DocElem{ 652 "filesystems", bson.D{{"$not", bson.D{{ 653 "$elemMatch", bson.D{{ 654 "$nin", machine.doc.Filesystems, 655 }}, 656 }}}}, 657 }) 658 } 659 } 660 return []txn.Op{{ 661 C: machinesC, 662 Id: machine.doc.Id, 663 Assert: assert, 664 Update: updates, 665 }}, nil 666 } 667 668 func hasJob(jobs []MachineJob, job MachineJob) bool { 669 for _, j := range jobs { 670 if j == job { 671 return true 672 } 673 } 674 return false 675 } 676 677 var errControllerNotAllowed = errors.New("controller jobs specified but not allowed") 678 679 // maintainControllersOps returns a set of operations that will maintain 680 // the controller information when the given machine documents 681 // are added to the machines collection. If currentInfo is nil, 682 // there can be only one machine document and it must have 683 // id 0 (this is a special case to allow adding the bootstrap machine) 684 func (st *State) maintainControllersOps(mdocs []*machineDoc, currentInfo *ControllerInfo) ([]txn.Op, error) { 685 var newIds, newVotingIds []string 686 for _, doc := range mdocs { 687 if !hasJob(doc.Jobs, JobManageModel) { 688 continue 689 } 690 newIds = append(newIds, doc.Id) 691 if !doc.NoVote { 692 newVotingIds = append(newVotingIds, doc.Id) 693 } 694 } 695 if len(newIds) == 0 { 696 return nil, nil 697 } 698 if currentInfo == nil { 699 // Allow bootstrap machine only. 700 if len(mdocs) != 1 || mdocs[0].Id != "0" { 701 return nil, errControllerNotAllowed 702 } 703 var err error 704 currentInfo, err = st.ControllerInfo() 705 if err != nil { 706 return nil, errors.Annotate(err, "cannot get controller info") 707 } 708 if len(currentInfo.MachineIds) > 0 || len(currentInfo.VotingMachineIds) > 0 { 709 return nil, errors.New("controllers already exist") 710 } 711 } 712 ops := []txn.Op{{ 713 C: controllersC, 714 Id: modelGlobalKey, 715 Assert: bson.D{{ 716 "$and", []bson.D{ 717 {{"machineids", bson.D{{"$size", len(currentInfo.MachineIds)}}}}, 718 {{"votingmachineids", bson.D{{"$size", len(currentInfo.VotingMachineIds)}}}}, 719 }, 720 }}, 721 Update: bson.D{ 722 {"$addToSet", bson.D{{"machineids", bson.D{{"$each", newIds}}}}}, 723 {"$addToSet", bson.D{{"votingmachineids", bson.D{{"$each", newVotingIds}}}}}, 724 }, 725 }} 726 return ops, nil 727 } 728 729 // EnableHA adds controller machines as necessary to make 730 // the number of live controllers equal to numControllers. The given 731 // constraints and series will be attached to any new machines. 732 // If placement is not empty, any new machines which may be required are started 733 // according to the specified placement directives until the placement list is 734 // exhausted; thereafter any new machines are started according to the constraints and series. 735 func (st *State) EnableHA( 736 numControllers int, cons constraints.Value, series string, placement []string, 737 ) (ControllersChanges, error) { 738 739 if numControllers < 0 || (numControllers != 0 && numControllers%2 != 1) { 740 return ControllersChanges{}, errors.New("number of controllers must be odd and non-negative") 741 } 742 if numControllers > replicaset.MaxPeers { 743 return ControllersChanges{}, errors.Errorf("controller count is too large (allowed %d)", replicaset.MaxPeers) 744 } 745 var change ControllersChanges 746 buildTxn := func(attempt int) ([]txn.Op, error) { 747 currentInfo, err := st.ControllerInfo() 748 if err != nil { 749 return nil, err 750 } 751 desiredControllerCount := numControllers 752 if desiredControllerCount == 0 { 753 desiredControllerCount = len(currentInfo.VotingMachineIds) 754 if desiredControllerCount <= 1 { 755 desiredControllerCount = 3 756 } 757 } 758 if len(currentInfo.VotingMachineIds) > desiredControllerCount { 759 return nil, errors.New("cannot reduce controller count") 760 } 761 762 intent, err := st.enableHAIntentions(currentInfo, placement) 763 if err != nil { 764 return nil, err 765 } 766 voteCount := 0 767 for _, m := range intent.maintain { 768 if m.WantsVote() { 769 voteCount++ 770 } 771 } 772 if voteCount == desiredControllerCount && len(intent.remove) == 0 { 773 return nil, jujutxn.ErrNoOperations 774 } 775 // Promote as many machines as we can to fulfil the shortfall. 776 if n := desiredControllerCount - voteCount; n < len(intent.promote) { 777 intent.promote = intent.promote[:n] 778 } 779 voteCount += len(intent.promote) 780 781 if n := desiredControllerCount - voteCount; n < len(intent.convert) { 782 intent.convert = intent.convert[:n] 783 } 784 voteCount += len(intent.convert) 785 786 intent.newCount = desiredControllerCount - voteCount 787 788 logger.Infof("%d new machines; promoting %v; converting %v", intent.newCount, intent.promote, intent.convert) 789 790 var ops []txn.Op 791 ops, change, err = st.enableHAIntentionOps(intent, currentInfo, cons, series) 792 return ops, err 793 } 794 if err := st.run(buildTxn); err != nil { 795 err = errors.Annotate(err, "failed to create new controller machines") 796 return ControllersChanges{}, err 797 } 798 return change, nil 799 } 800 801 // Change in controllers after the ensure availability txn has committed. 802 type ControllersChanges struct { 803 Added []string 804 Removed []string 805 Maintained []string 806 Promoted []string 807 Demoted []string 808 Converted []string 809 } 810 811 // enableHAIntentionOps returns operations to fulfil the desired intent. 812 func (st *State) enableHAIntentionOps( 813 intent *enableHAIntent, 814 currentInfo *ControllerInfo, 815 cons constraints.Value, 816 series string, 817 ) ([]txn.Op, ControllersChanges, error) { 818 var ops []txn.Op 819 var change ControllersChanges 820 for _, m := range intent.promote { 821 ops = append(ops, promoteControllerOps(m)...) 822 change.Promoted = append(change.Promoted, m.doc.Id) 823 } 824 for _, m := range intent.demote { 825 ops = append(ops, demoteControllerOps(m)...) 826 change.Demoted = append(change.Demoted, m.doc.Id) 827 } 828 for _, m := range intent.convert { 829 ops = append(ops, convertControllerOps(m)...) 830 change.Converted = append(change.Converted, m.doc.Id) 831 } 832 // Use any placement directives that have been provided 833 // when adding new machines, until the directives have 834 // been all used up. Set up a helper function to do the 835 // work required. 836 placementCount := 0 837 getPlacement := func() string { 838 if placementCount >= len(intent.placement) { 839 return "" 840 } 841 result := intent.placement[placementCount] 842 placementCount++ 843 return result 844 } 845 mdocs := make([]*machineDoc, intent.newCount) 846 for i := range mdocs { 847 template := MachineTemplate{ 848 Series: series, 849 Jobs: []MachineJob{ 850 JobHostUnits, 851 JobManageModel, 852 }, 853 Constraints: cons, 854 Placement: getPlacement(), 855 } 856 mdoc, addOps, err := st.addMachineOps(template) 857 if err != nil { 858 return nil, ControllersChanges{}, err 859 } 860 mdocs[i] = mdoc 861 ops = append(ops, addOps...) 862 change.Added = append(change.Added, mdoc.Id) 863 864 } 865 for _, m := range intent.remove { 866 ops = append(ops, removeControllerOps(m)...) 867 change.Removed = append(change.Removed, m.doc.Id) 868 869 } 870 871 for _, m := range intent.maintain { 872 tag, err := names.ParseTag(m.Tag().String()) 873 if err != nil { 874 return nil, ControllersChanges{}, errors.Annotate(err, "could not parse machine tag") 875 } 876 if tag.Kind() != names.MachineTagKind { 877 return nil, ControllersChanges{}, errors.Errorf("expected machine tag kind, got %s", tag.Kind()) 878 } 879 change.Maintained = append(change.Maintained, tag.Id()) 880 } 881 ssOps, err := st.maintainControllersOps(mdocs, currentInfo) 882 if err != nil { 883 return nil, ControllersChanges{}, errors.Annotate(err, "cannot prepare machine add operations") 884 } 885 ops = append(ops, ssOps...) 886 return ops, change, nil 887 } 888 889 // controllerAvailable returns true if the specified controller machine is 890 // available. 891 var controllerAvailable = func(m *Machine) (bool, error) { 892 // TODO(axw) #1271504 2014-01-22 893 // Check the controller's associated mongo health; 894 // requires coordination with worker/peergrouper. 895 return m.AgentPresence() 896 } 897 898 type enableHAIntent struct { 899 newCount int 900 placement []string 901 902 promote, maintain, demote, remove, convert []*Machine 903 } 904 905 // enableHAIntentions returns what we would like 906 // to do to maintain the availability of the existing servers 907 // mentioned in the given info, including: 908 // demoting unavailable, voting machines; 909 // removing unavailable, non-voting, non-vote-holding machines; 910 // gathering available, non-voting machines that may be promoted; 911 func (st *State) enableHAIntentions(info *ControllerInfo, placement []string) (*enableHAIntent, error) { 912 var intent enableHAIntent 913 for _, s := range placement { 914 // TODO(natefinch): unscoped placements shouldn't ever get here (though 915 // they do currently). We should fix up the CLI to always add a scope 916 // to placements and then we can remove the need to deal with unscoped 917 // placements. 918 p, err := instance.ParsePlacement(s) 919 if err == instance.ErrPlacementScopeMissing { 920 intent.placement = append(intent.placement, s) 921 continue 922 } 923 if err == nil && p.Scope == instance.MachineScope { 924 // TODO(natefinch) add env provider policy to check if conversion is 925 // possible (e.g. cannot be supported by Azure in HA mode). 926 927 if names.IsContainerMachine(p.Directive) { 928 return nil, errors.New("container placement directives not supported") 929 } 930 931 m, err := st.Machine(p.Directive) 932 if err != nil { 933 return nil, errors.Annotatef(err, "can't find machine for placement directive %q", s) 934 } 935 if m.IsManager() { 936 return nil, errors.Errorf("machine for placement directive %q is already a controller", s) 937 } 938 intent.convert = append(intent.convert, m) 939 intent.placement = append(intent.placement, s) 940 continue 941 } 942 return nil, errors.Errorf("unsupported placement directive %q", s) 943 } 944 945 for _, mid := range info.MachineIds { 946 m, err := st.Machine(mid) 947 if err != nil { 948 return nil, err 949 } 950 available, err := controllerAvailable(m) 951 if err != nil { 952 return nil, err 953 } 954 logger.Infof("machine %q, available %v, wants vote %v, has vote %v", m, available, m.WantsVote(), m.HasVote()) 955 if available { 956 if m.WantsVote() { 957 intent.maintain = append(intent.maintain, m) 958 } else { 959 intent.promote = append(intent.promote, m) 960 } 961 continue 962 } 963 if m.WantsVote() { 964 // The machine wants to vote, so we simply set novote and allow it 965 // to run its course to have its vote removed by the worker that 966 // maintains the replicaset. We will replace it with an existing 967 // non-voting controller if there is one, starting a new one if 968 // not. 969 intent.demote = append(intent.demote, m) 970 } else if m.HasVote() { 971 // The machine still has a vote, so keep it around for now. 972 intent.maintain = append(intent.maintain, m) 973 } else { 974 // The machine neither wants to nor has a vote, so remove its 975 // JobManageModel job immediately. 976 intent.remove = append(intent.remove, m) 977 } 978 } 979 logger.Infof("initial intentions: promote %v; maintain %v; demote %v; remove %v; convert: %v", 980 intent.promote, intent.maintain, intent.demote, intent.remove, intent.convert) 981 return &intent, nil 982 } 983 984 func convertControllerOps(m *Machine) []txn.Op { 985 return []txn.Op{{ 986 C: machinesC, 987 Id: m.doc.DocID, 988 Update: bson.D{ 989 {"$addToSet", bson.D{{"jobs", JobManageModel}}}, 990 {"$set", bson.D{{"novote", false}}}, 991 }, 992 Assert: bson.D{{"jobs", bson.D{{"$nin", []MachineJob{JobManageModel}}}}}, 993 }, { 994 C: controllersC, 995 Id: modelGlobalKey, 996 Update: bson.D{ 997 {"$addToSet", bson.D{{"votingmachineids", m.doc.Id}}}, 998 {"$addToSet", bson.D{{"machineids", m.doc.Id}}}, 999 }, 1000 }} 1001 } 1002 1003 func promoteControllerOps(m *Machine) []txn.Op { 1004 return []txn.Op{{ 1005 C: machinesC, 1006 Id: m.doc.DocID, 1007 Assert: bson.D{{"novote", true}}, 1008 Update: bson.D{{"$set", bson.D{{"novote", false}}}}, 1009 }, { 1010 C: controllersC, 1011 Id: modelGlobalKey, 1012 Update: bson.D{{"$addToSet", bson.D{{"votingmachineids", m.doc.Id}}}}, 1013 }} 1014 } 1015 1016 func demoteControllerOps(m *Machine) []txn.Op { 1017 return []txn.Op{{ 1018 C: machinesC, 1019 Id: m.doc.DocID, 1020 Assert: bson.D{{"novote", false}}, 1021 Update: bson.D{{"$set", bson.D{{"novote", true}}}}, 1022 }, { 1023 C: controllersC, 1024 Id: modelGlobalKey, 1025 Update: bson.D{{"$pull", bson.D{{"votingmachineids", m.doc.Id}}}}, 1026 }} 1027 } 1028 1029 func removeControllerOps(m *Machine) []txn.Op { 1030 return []txn.Op{{ 1031 C: machinesC, 1032 Id: m.doc.DocID, 1033 Assert: bson.D{{"novote", true}, {"hasvote", false}}, 1034 Update: bson.D{ 1035 {"$pull", bson.D{{"jobs", JobManageModel}}}, 1036 {"$set", bson.D{{"novote", false}}}, 1037 }, 1038 }, { 1039 C: controllersC, 1040 Id: modelGlobalKey, 1041 Update: bson.D{{"$pull", bson.D{{"machineids", m.doc.Id}}}}, 1042 }} 1043 }