github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/state/machine.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 "net" 9 "strings" 10 "time" 11 12 "github.com/juju/errors" 13 "github.com/juju/names" 14 jujutxn "github.com/juju/txn" 15 "github.com/juju/utils" 16 "github.com/juju/utils/set" 17 "gopkg.in/mgo.v2" 18 "gopkg.in/mgo.v2/bson" 19 "gopkg.in/mgo.v2/txn" 20 21 "github.com/juju/juju/constraints" 22 "github.com/juju/juju/instance" 23 "github.com/juju/juju/mongo" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/state/multiwatcher" 26 "github.com/juju/juju/state/presence" 27 "github.com/juju/juju/tools" 28 "github.com/juju/juju/version" 29 ) 30 31 // Machine represents the state of a machine. 32 type Machine struct { 33 st *State 34 doc machineDoc 35 presence.Presencer 36 } 37 38 // MachineJob values define responsibilities that machines may be 39 // expected to fulfil. 40 type MachineJob int 41 42 const ( 43 _ MachineJob = iota 44 JobHostUnits 45 JobManageEnviron 46 JobManageNetworking 47 48 // Deprecated in 1.18. 49 JobManageStateDeprecated 50 ) 51 52 var jobNames = map[MachineJob]multiwatcher.MachineJob{ 53 JobHostUnits: multiwatcher.JobHostUnits, 54 JobManageEnviron: multiwatcher.JobManageEnviron, 55 JobManageNetworking: multiwatcher.JobManageNetworking, 56 57 // Deprecated in 1.18. 58 JobManageStateDeprecated: multiwatcher.JobManageStateDeprecated, 59 } 60 61 // AllJobs returns all supported machine jobs. 62 func AllJobs() []MachineJob { 63 return []MachineJob{ 64 JobHostUnits, 65 JobManageEnviron, 66 JobManageNetworking, 67 } 68 } 69 70 // ToParams returns the job as multiwatcher.MachineJob. 71 func (job MachineJob) ToParams() multiwatcher.MachineJob { 72 if jujuJob, ok := jobNames[job]; ok { 73 return jujuJob 74 } 75 return multiwatcher.MachineJob(fmt.Sprintf("<unknown job %d>", int(job))) 76 } 77 78 // params.JobsFromJobs converts state jobs to juju jobs. 79 func paramsJobsFromJobs(jobs []MachineJob) []multiwatcher.MachineJob { 80 jujuJobs := make([]multiwatcher.MachineJob, len(jobs)) 81 for i, machineJob := range jobs { 82 jujuJobs[i] = machineJob.ToParams() 83 } 84 return jujuJobs 85 } 86 87 func (job MachineJob) String() string { 88 return string(job.ToParams()) 89 } 90 91 // manualMachinePrefix signals as prefix of Nonce that a machine is 92 // manually provisioned. 93 const manualMachinePrefix = "manual:" 94 95 // machineDoc represents the internal state of a machine in MongoDB. 96 // Note the correspondence with MachineInfo in apiserver/juju. 97 type machineDoc struct { 98 DocID string `bson:"_id"` 99 Id string `bson:"machineid"` 100 EnvUUID string `bson:"env-uuid"` 101 Nonce string 102 Series string 103 ContainerType string 104 Principals []string 105 Life Life 106 Tools *tools.Tools `bson:",omitempty"` 107 Jobs []MachineJob 108 NoVote bool 109 HasVote bool 110 PasswordHash string 111 Clean bool 112 // We store 2 different sets of addresses for the machine, obtained 113 // from different sources. 114 // Addresses is the set of addresses obtained by asking the provider. 115 Addresses []address 116 // MachineAddresses is the set of addresses obtained from the machine itself. 117 MachineAddresses []address 118 // The SupportedContainers attributes are used to advertise what containers this 119 // machine is capable of hosting. 120 SupportedContainersKnown bool 121 SupportedContainers []instance.ContainerType `bson:",omitempty"` 122 // Placement is the placement directive that should be used when provisioning 123 // an instance for the machine. 124 Placement string `bson:",omitempty"` 125 } 126 127 func newMachine(st *State, doc *machineDoc) *Machine { 128 machine := &Machine{ 129 st: st, 130 doc: *doc, 131 } 132 return machine 133 } 134 135 func wantsVote(jobs []MachineJob, noVote bool) bool { 136 return hasJob(jobs, JobManageEnviron) && !noVote 137 } 138 139 // Id returns the machine id. 140 func (m *Machine) Id() string { 141 return m.doc.Id 142 } 143 144 // Series returns the operating system series running on the machine. 145 func (m *Machine) Series() string { 146 return m.doc.Series 147 } 148 149 // ContainerType returns the type of container hosting this machine. 150 func (m *Machine) ContainerType() instance.ContainerType { 151 return instance.ContainerType(m.doc.ContainerType) 152 } 153 154 // machineGlobalKey returns the global database key for the identified machine. 155 func machineGlobalKey(id string) string { 156 return "m#" + id 157 } 158 159 // globalKey returns the global database key for the machine. 160 func (m *Machine) globalKey() string { 161 return machineGlobalKey(m.doc.Id) 162 } 163 164 // instanceData holds attributes relevant to a provisioned machine. 165 type instanceData struct { 166 DocID string `bson:"_id"` 167 MachineId string `bson:"machineid"` 168 InstanceId instance.Id `bson:"instanceid"` 169 EnvUUID string `bson:"env-uuid"` 170 Status string `bson:"status,omitempty"` 171 Arch *string `bson:"arch,omitempty"` 172 Mem *uint64 `bson:"mem,omitempty"` 173 RootDisk *uint64 `bson:"rootdisk,omitempty"` 174 CpuCores *uint64 `bson:"cpucores,omitempty"` 175 CpuPower *uint64 `bson:"cpupower,omitempty"` 176 Tags *[]string `bson:"tags,omitempty"` 177 AvailZone *string `bson:"availzone,omitempty"` 178 } 179 180 func hardwareCharacteristics(instData instanceData) *instance.HardwareCharacteristics { 181 return &instance.HardwareCharacteristics{ 182 Arch: instData.Arch, 183 Mem: instData.Mem, 184 RootDisk: instData.RootDisk, 185 CpuCores: instData.CpuCores, 186 CpuPower: instData.CpuPower, 187 Tags: instData.Tags, 188 AvailabilityZone: instData.AvailZone, 189 } 190 } 191 192 // TODO(wallyworld): move this method to a service. 193 func (m *Machine) HardwareCharacteristics() (*instance.HardwareCharacteristics, error) { 194 instData, err := getInstanceData(m.st, m.Id()) 195 if err != nil { 196 return nil, err 197 } 198 return hardwareCharacteristics(instData), nil 199 } 200 201 func getInstanceData(st *State, id string) (instanceData, error) { 202 instanceDataCollection, closer := st.getCollection(instanceDataC) 203 defer closer() 204 205 var instData instanceData 206 err := instanceDataCollection.FindId(id).One(&instData) 207 if err == mgo.ErrNotFound { 208 return instanceData{}, errors.NotFoundf("instance data for machine %v", id) 209 } 210 if err != nil { 211 return instanceData{}, fmt.Errorf("cannot get instance data for machine %v: %v", id, err) 212 } 213 return instData, nil 214 } 215 216 // Tag returns a tag identifying the machine. The String method provides a 217 // string representation that is safe to use as a file name. The returned name 218 // will be different from other Tag values returned by any other entities 219 // from the same state. 220 func (m *Machine) Tag() names.Tag { 221 return m.MachineTag() 222 } 223 224 // MachineTag returns the more specific MachineTag type as opposed 225 // to the more generic Tag type. 226 func (m *Machine) MachineTag() names.MachineTag { 227 return names.NewMachineTag(m.Id()) 228 } 229 230 // Life returns whether the machine is Alive, Dying or Dead. 231 func (m *Machine) Life() Life { 232 return m.doc.Life 233 } 234 235 // Jobs returns the responsibilities that must be fulfilled by m's agent. 236 func (m *Machine) Jobs() []MachineJob { 237 return m.doc.Jobs 238 } 239 240 // WantsVote reports whether the machine is a state server 241 // that wants to take part in peer voting. 242 func (m *Machine) WantsVote() bool { 243 return wantsVote(m.doc.Jobs, m.doc.NoVote) 244 } 245 246 // HasVote reports whether that machine is currently a voting 247 // member of the replica set. 248 func (m *Machine) HasVote() bool { 249 return m.doc.HasVote 250 } 251 252 // SetHasVote sets whether the machine is currently a voting 253 // member of the replica set. It should only be called 254 // from the worker that maintains the replica set. 255 func (m *Machine) SetHasVote(hasVote bool) error { 256 ops := []txn.Op{{ 257 C: machinesC, 258 Id: m.doc.DocID, 259 Assert: notDeadDoc, 260 Update: bson.D{{"$set", bson.D{{"hasvote", hasVote}}}}, 261 }} 262 if err := m.st.runTransaction(ops); err != nil { 263 return fmt.Errorf("cannot set HasVote of machine %v: %v", m, onAbort(err, ErrDead)) 264 } 265 m.doc.HasVote = hasVote 266 return nil 267 } 268 269 // IsManager returns true if the machine has JobManageEnviron. 270 func (m *Machine) IsManager() bool { 271 return hasJob(m.doc.Jobs, JobManageEnviron) 272 } 273 274 // IsManual returns true if the machine was manually provisioned. 275 func (m *Machine) IsManual() (bool, error) { 276 // Apart from the bootstrap machine, manually provisioned 277 // machines have a nonce prefixed with "manual:". This is 278 // unique to manual provisioning. 279 if strings.HasPrefix(m.doc.Nonce, manualMachinePrefix) { 280 return true, nil 281 } 282 // The bootstrap machine uses BootstrapNonce, so in that 283 // case we need to check if its provider type is "manual". 284 // We also check for "null", which is an alias for manual. 285 if m.doc.Id == "0" { 286 cfg, err := m.st.EnvironConfig() 287 if err != nil { 288 return false, err 289 } 290 t := cfg.Type() 291 return t == "null" || t == "manual", nil 292 } 293 return false, nil 294 } 295 296 // AgentTools returns the tools that the agent is currently running. 297 // It returns an error that satisfies errors.IsNotFound if the tools 298 // have not yet been set. 299 func (m *Machine) AgentTools() (*tools.Tools, error) { 300 if m.doc.Tools == nil { 301 return nil, errors.NotFoundf("agent tools for machine %v", m) 302 } 303 tools := *m.doc.Tools 304 return &tools, nil 305 } 306 307 // checkVersionValidity checks whether the given version is suitable 308 // for passing to SetAgentVersion. 309 func checkVersionValidity(v version.Binary) error { 310 if v.Series == "" || v.Arch == "" { 311 return fmt.Errorf("empty series or arch") 312 } 313 return nil 314 } 315 316 // SetAgentVersion sets the version of juju that the agent is 317 // currently running. 318 func (m *Machine) SetAgentVersion(v version.Binary) (err error) { 319 defer errors.DeferredAnnotatef(&err, "cannot set agent version for machine %v", m) 320 if err = checkVersionValidity(v); err != nil { 321 return err 322 } 323 tools := &tools.Tools{Version: v} 324 ops := []txn.Op{{ 325 C: machinesC, 326 Id: m.doc.DocID, 327 Assert: notDeadDoc, 328 Update: bson.D{{"$set", bson.D{{"tools", tools}}}}, 329 }} 330 // A "raw" transaction is needed here because this function gets 331 // called before database migraions have run so we don't 332 // necessarily want the env UUID added to the id. 333 if err := m.st.runRawTransaction(ops); err != nil { 334 return onAbort(err, ErrDead) 335 } 336 m.doc.Tools = tools 337 return nil 338 } 339 340 // SetMongoPassword sets the password the agent responsible for the machine 341 // should use to communicate with the state servers. Previous passwords 342 // are invalidated. 343 func (m *Machine) SetMongoPassword(password string) error { 344 if !m.IsManager() { 345 return errors.NotSupportedf("setting mongo password for non-state server machine %v", m) 346 } 347 return mongo.SetAdminMongoPassword(m.st.db.Session, m.Tag().String(), password) 348 } 349 350 // SetPassword sets the password for the machine's agent. 351 func (m *Machine) SetPassword(password string) error { 352 if len(password) < utils.MinAgentPasswordLength { 353 return fmt.Errorf("password is only %d bytes long, and is not a valid Agent password", len(password)) 354 } 355 return m.setPasswordHash(utils.AgentPasswordHash(password)) 356 } 357 358 // setPasswordHash sets the underlying password hash in the database directly 359 // to the value supplied. This is split out from SetPassword to allow direct 360 // manipulation in tests (to check for backwards compatibility). 361 func (m *Machine) setPasswordHash(passwordHash string) error { 362 ops := []txn.Op{{ 363 C: machinesC, 364 Id: m.doc.DocID, 365 Assert: notDeadDoc, 366 Update: bson.D{{"$set", bson.D{{"passwordhash", passwordHash}}}}, 367 }} 368 if err := m.st.runTransaction(ops); err != nil { 369 return fmt.Errorf("cannot set password of machine %v: %v", m, onAbort(err, ErrDead)) 370 } 371 m.doc.PasswordHash = passwordHash 372 return nil 373 } 374 375 // Return the underlying PasswordHash stored in the database. Used by the test 376 // suite to check that the PasswordHash gets properly updated to new values 377 // when compatibility mode is detected. 378 func (m *Machine) getPasswordHash() string { 379 return m.doc.PasswordHash 380 } 381 382 // PasswordValid returns whether the given password is valid 383 // for the given machine. 384 func (m *Machine) PasswordValid(password string) bool { 385 agentHash := utils.AgentPasswordHash(password) 386 if agentHash == m.doc.PasswordHash { 387 return true 388 } 389 // In Juju 1.16 and older we used the slower password hash for unit 390 // agents. So check to see if the supplied password matches the old 391 // path, and if so, update it to the new mechanism. 392 // We ignore any error in setting the password, as we'll just try again 393 // next time 394 if utils.UserPasswordHash(password, utils.CompatSalt) == m.doc.PasswordHash { 395 logger.Debugf("%s logged in with old password hash, changing to AgentPasswordHash", 396 m.Tag()) 397 m.setPasswordHash(agentHash) 398 return true 399 } 400 return false 401 } 402 403 // Destroy sets the machine lifecycle to Dying if it is Alive. It does 404 // nothing otherwise. Destroy will fail if the machine has principal 405 // units assigned, or if the machine has JobManageEnviron. 406 // If the machine has assigned units, Destroy will return 407 // a HasAssignedUnitsError. 408 func (m *Machine) Destroy() error { 409 return m.advanceLifecycle(Dying) 410 } 411 412 // ForceDestroy queues the machine for complete removal, including the 413 // destruction of all units and containers on the machine. 414 func (m *Machine) ForceDestroy() error { 415 if !m.IsManager() { 416 ops := []txn.Op{{ 417 C: machinesC, 418 Id: m.doc.DocID, 419 Assert: bson.D{{"jobs", bson.D{{"$nin", []MachineJob{JobManageEnviron}}}}}, 420 }, m.st.newCleanupOp(cleanupForceDestroyedMachine, m.doc.Id)} 421 if err := m.st.runTransaction(ops); err != txn.ErrAborted { 422 return err 423 } 424 } 425 return fmt.Errorf("machine %s is required by the environment", m.doc.Id) 426 } 427 428 // EnsureDead sets the machine lifecycle to Dead if it is Alive or Dying. 429 // It does nothing otherwise. EnsureDead will fail if the machine has 430 // principal units assigned, or if the machine has JobManageEnviron. 431 // If the machine has assigned units, EnsureDead will return 432 // a HasAssignedUnitsError. 433 func (m *Machine) EnsureDead() error { 434 return m.advanceLifecycle(Dead) 435 } 436 437 type HasAssignedUnitsError struct { 438 MachineId string 439 UnitNames []string 440 } 441 442 func (e *HasAssignedUnitsError) Error() string { 443 return fmt.Sprintf("machine %s has unit %q assigned", e.MachineId, e.UnitNames[0]) 444 } 445 446 func IsHasAssignedUnitsError(err error) bool { 447 _, ok := err.(*HasAssignedUnitsError) 448 return ok 449 } 450 451 // Containers returns the container ids belonging to a parent machine. 452 // TODO(wallyworld): move this method to a service 453 func (m *Machine) Containers() ([]string, error) { 454 containerRefs, closer := m.st.getCollection(containerRefsC) 455 defer closer() 456 457 var mc machineContainers 458 err := containerRefs.FindId(m.doc.DocID).One(&mc) 459 if err == nil { 460 return mc.Children, nil 461 } 462 if err == mgo.ErrNotFound { 463 return nil, errors.NotFoundf("container info for machine %v", m.Id()) 464 } 465 return nil, err 466 } 467 468 // ParentId returns the Id of the host machine if this machine is a container. 469 func (m *Machine) ParentId() (string, bool) { 470 parentId := ParentId(m.Id()) 471 return parentId, parentId != "" 472 } 473 474 // IsContainer returns true if the machine is a container. 475 func (m *Machine) IsContainer() bool { 476 _, isContainer := m.ParentId() 477 return isContainer 478 } 479 480 type HasContainersError struct { 481 MachineId string 482 ContainerIds []string 483 } 484 485 func (e *HasContainersError) Error() string { 486 return fmt.Sprintf("machine %s is hosting containers %q", e.MachineId, strings.Join(e.ContainerIds, ",")) 487 } 488 489 func IsHasContainersError(err error) bool { 490 _, ok := err.(*HasContainersError) 491 return ok 492 } 493 494 // advanceLifecycle ensures that the machine's lifecycle is no earlier 495 // than the supplied value. If the machine already has that lifecycle 496 // value, or a later one, no changes will be made to remote state. If 497 // the machine has any responsibilities that preclude a valid change in 498 // lifecycle, it will return an error. 499 func (original *Machine) advanceLifecycle(life Life) (err error) { 500 containers, err := original.Containers() 501 if err != nil { 502 return err 503 } 504 if len(containers) > 0 { 505 return &HasContainersError{ 506 MachineId: original.doc.Id, 507 ContainerIds: containers, 508 } 509 } 510 m := original 511 defer func() { 512 if err == nil { 513 // The machine's lifecycle is known to have advanced; it may be 514 // known to have already advanced further than requested, in 515 // which case we set the latest known valid value. 516 if m == nil { 517 life = Dead 518 } else if m.doc.Life > life { 519 life = m.doc.Life 520 } 521 original.doc.Life = life 522 } 523 }() 524 // op and 525 op := txn.Op{ 526 C: machinesC, 527 Id: m.doc.DocID, 528 Update: bson.D{{"$set", bson.D{{"life", life}}}}, 529 } 530 advanceAsserts := bson.D{ 531 {"jobs", bson.D{{"$nin", []MachineJob{JobManageEnviron}}}}, 532 {"$or", []bson.D{ 533 {{"principals", bson.D{{"$size", 0}}}}, 534 {{"principals", bson.D{{"$exists", false}}}}, 535 }}, 536 {"hasvote", bson.D{{"$ne", true}}}, 537 } 538 // multiple attempts: one with original data, one with refreshed data, and a final 539 // one intended to determine the cause of failure of the preceding attempt. 540 buildTxn := func(attempt int) ([]txn.Op, error) { 541 // If the transaction was aborted, grab a fresh copy of the machine data. 542 // We don't write to original, because the expectation is that state- 543 // changing methods only set the requested change on the receiver; a case 544 // could perhaps be made that this is not a helpful convention in the 545 // context of the new state API, but we maintain consistency in the 546 // face of uncertainty. 547 if attempt != 0 { 548 if m, err = m.st.Machine(m.doc.Id); errors.IsNotFound(err) { 549 return nil, jujutxn.ErrNoOperations 550 } else if err != nil { 551 return nil, err 552 } 553 } 554 // Check that the life change is sane, and collect the assertions 555 // necessary to determine that it remains so. 556 switch life { 557 case Dying: 558 if m.doc.Life != Alive { 559 return nil, jujutxn.ErrNoOperations 560 } 561 op.Assert = append(advanceAsserts, isAliveDoc...) 562 case Dead: 563 if m.doc.Life == Dead { 564 return nil, jujutxn.ErrNoOperations 565 } 566 op.Assert = append(advanceAsserts, notDeadDoc...) 567 default: 568 panic(fmt.Errorf("cannot advance lifecycle to %v", life)) 569 } 570 // Check that the machine does not have any responsibilities that 571 // prevent a lifecycle change. 572 if hasJob(m.doc.Jobs, JobManageEnviron) { 573 // (NOTE: When we enable multiple JobManageEnviron machines, 574 // this restriction will be lifted, but we will assert that the 575 // machine is not voting) 576 return nil, fmt.Errorf("machine %s is required by the environment", m.doc.Id) 577 } 578 if m.doc.HasVote { 579 return nil, fmt.Errorf("machine %s is a voting replica set member", m.doc.Id) 580 } 581 if len(m.doc.Principals) != 0 { 582 return nil, &HasAssignedUnitsError{ 583 MachineId: m.doc.Id, 584 UnitNames: m.doc.Principals, 585 } 586 } 587 return []txn.Op{op}, nil 588 } 589 if err = m.st.run(buildTxn); err == jujutxn.ErrExcessiveContention { 590 err = errors.Annotatef(err, "machine %s cannot advance lifecycle", m) 591 } 592 return err 593 } 594 595 func (m *Machine) removePortsOps() ([]txn.Op, error) { 596 if m.doc.Life != Dead { 597 return nil, errors.Errorf("machine is not dead") 598 } 599 ports, err := m.AllPorts() 600 if err != nil { 601 return nil, err 602 } 603 var ops []txn.Op 604 for _, p := range ports { 605 ops = append(ops, p.removeOps()...) 606 } 607 return ops, nil 608 } 609 610 func (m *Machine) removeNetworkInterfacesOps() ([]txn.Op, error) { 611 if m.doc.Life != Dead { 612 return nil, errors.Errorf("machine is not dead") 613 } 614 ops := []txn.Op{{ 615 C: machinesC, 616 Id: m.doc.DocID, 617 Assert: isDeadDoc, 618 }} 619 sel := bson.D{{"machineid", m.doc.Id}} 620 networkInterfaces, closer := m.st.getCollection(networkInterfacesC) 621 defer closer() 622 623 iter := networkInterfaces.Find(sel).Select(bson.D{{"_id", 1}}).Iter() 624 var doc networkInterfaceDoc 625 for iter.Next(&doc) { 626 ops = append(ops, txn.Op{ 627 C: networkInterfacesC, 628 Id: doc.Id, 629 Remove: true, 630 }) 631 } 632 return ops, iter.Close() 633 } 634 635 // Remove removes the machine from state. It will fail if the machine 636 // is not Dead. 637 func (m *Machine) Remove() (err error) { 638 defer errors.DeferredAnnotatef(&err, "cannot remove machine %s", m.doc.Id) 639 if m.doc.Life != Dead { 640 return fmt.Errorf("machine is not dead") 641 } 642 ops := []txn.Op{ 643 { 644 C: machinesC, 645 Id: m.doc.DocID, 646 Assert: txn.DocExists, 647 Remove: true, 648 }, 649 { 650 C: machinesC, 651 Id: m.doc.DocID, 652 Assert: isDeadDoc, 653 }, 654 { 655 C: instanceDataC, 656 Id: m.doc.DocID, 657 Remove: true, 658 }, 659 removeStatusOp(m.st, m.globalKey()), 660 removeConstraintsOp(m.st, m.globalKey()), 661 removeRequestedNetworksOp(m.st, m.globalKey()), 662 annotationRemoveOp(m.st, m.globalKey()), 663 removeRebootDocOp(m.st, m.globalKey()), 664 } 665 ifacesOps, err := m.removeNetworkInterfacesOps() 666 if err != nil { 667 return err 668 } 669 portsOps, err := m.removePortsOps() 670 if err != nil { 671 return err 672 } 673 blockDeviceOps, err := removeMachineBlockDevicesOps(m.st, m.Id()) 674 if err != nil { 675 return err 676 } 677 ops = append(ops, ifacesOps...) 678 ops = append(ops, portsOps...) 679 ops = append(ops, blockDeviceOps...) 680 ops = append(ops, removeContainerRefOps(m.st, m.Id())...) 681 // The only abort conditions in play indicate that the machine has already 682 // been removed. 683 return onAbort(m.st.runTransaction(ops), nil) 684 } 685 686 // Refresh refreshes the contents of the machine from the underlying 687 // state. It returns an error that satisfies errors.IsNotFound if the 688 // machine has been removed. 689 func (m *Machine) Refresh() error { 690 machines, closer := m.st.getCollection(machinesC) 691 defer closer() 692 693 var doc machineDoc 694 err := machines.FindId(m.doc.DocID).One(&doc) 695 if err == mgo.ErrNotFound { 696 return errors.NotFoundf("machine %v", m) 697 } 698 if err != nil { 699 return errors.Annotatef(err, "cannot refresh machine %v", m) 700 } 701 m.doc = doc 702 return nil 703 } 704 705 // AgentPresence returns whether the respective remote agent is alive. 706 func (m *Machine) AgentPresence() (bool, error) { 707 b, err := m.st.pwatcher.Alive(m.globalKey()) 708 return b, err 709 } 710 711 // WaitAgentPresence blocks until the respective agent is alive. 712 func (m *Machine) WaitAgentPresence(timeout time.Duration) (err error) { 713 defer errors.DeferredAnnotatef(&err, "waiting for agent of machine %v", m) 714 ch := make(chan presence.Change) 715 m.st.pwatcher.Watch(m.globalKey(), ch) 716 defer m.st.pwatcher.Unwatch(m.globalKey(), ch) 717 for i := 0; i < 2; i++ { 718 select { 719 case change := <-ch: 720 if change.Alive { 721 return nil 722 } 723 case <-time.After(timeout): 724 return fmt.Errorf("still not alive after timeout") 725 case <-m.st.pwatcher.Dead(): 726 return m.st.pwatcher.Err() 727 } 728 } 729 panic(fmt.Sprintf("presence reported dead status twice in a row for machine %v", m)) 730 } 731 732 // SetAgentPresence signals that the agent for machine m is alive. 733 // It returns the started pinger. 734 func (m *Machine) SetAgentPresence() (*presence.Pinger, error) { 735 presenceCollection := m.st.getPresence() 736 p := presence.NewPinger(presenceCollection, m.st.environTag, m.globalKey()) 737 err := p.Start() 738 if err != nil { 739 return nil, err 740 } 741 // We preform a manual sync here so that the 742 // presence pinger has the most up-to-date information when it 743 // starts. This ensures that commands run immediately after bootstrap 744 // like status or ensure-availability will have an accurate values 745 // for agent-state. 746 // 747 // TODO: Does not work for multiple state servers. Trigger a sync across all state servers. 748 if m.IsManager() { 749 m.st.pwatcher.Sync() 750 } 751 return p, nil 752 } 753 754 // InstanceId returns the provider specific instance id for this 755 // machine, or a NotProvisionedError, if not set. 756 func (m *Machine) InstanceId() (instance.Id, error) { 757 instData, err := getInstanceData(m.st, m.Id()) 758 if errors.IsNotFound(err) { 759 err = errors.NotProvisionedf("machine %v", m.Id()) 760 } 761 if err != nil { 762 return "", err 763 } 764 return instData.InstanceId, err 765 } 766 767 // InstanceStatus returns the provider specific instance status for this machine, 768 // or a NotProvisionedError if instance is not yet provisioned. 769 func (m *Machine) InstanceStatus() (string, error) { 770 instData, err := getInstanceData(m.st, m.Id()) 771 if errors.IsNotFound(err) { 772 err = errors.NotProvisionedf("machine %v", m.Id()) 773 } 774 if err != nil { 775 return "", err 776 } 777 return instData.Status, err 778 } 779 780 // SetInstanceStatus sets the provider specific instance status for a machine. 781 func (m *Machine) SetInstanceStatus(status string) (err error) { 782 defer errors.DeferredAnnotatef(&err, "cannot set instance status for machine %q", m) 783 784 ops := []txn.Op{ 785 { 786 C: instanceDataC, 787 Id: m.doc.DocID, 788 Assert: txn.DocExists, 789 Update: bson.D{{"$set", bson.D{{"status", status}}}}, 790 }, 791 } 792 793 if err = m.st.runTransaction(ops); err == nil { 794 return nil 795 } else if err != txn.ErrAborted { 796 return err 797 } 798 return errors.NotProvisionedf("machine %v", m.Id()) 799 } 800 801 // AvailabilityZone returns the provier-specific instance availability 802 // zone in which the machine was provisioned. 803 func (m *Machine) AvailabilityZone() (string, error) { 804 instData, err := getInstanceData(m.st, m.Id()) 805 if errors.IsNotFound(err) { 806 return "", errors.Trace(errors.NotProvisionedf("machine %v", m.Id())) 807 } 808 if err != nil { 809 return "", errors.Trace(err) 810 } 811 var zone string 812 if instData.AvailZone != nil { 813 zone = *instData.AvailZone 814 } 815 return zone, nil 816 } 817 818 // Units returns all the units that have been assigned to the machine. 819 func (m *Machine) Units() (units []*Unit, err error) { 820 defer errors.DeferredAnnotatef(&err, "cannot get units assigned to machine %v", m) 821 unitsCollection, closer := m.st.getCollection(unitsC) 822 defer closer() 823 824 pudocs := []unitDoc{} 825 err = unitsCollection.Find(bson.D{{"machineid", m.doc.Id}}).All(&pudocs) 826 if err != nil { 827 return nil, err 828 } 829 for _, pudoc := range pudocs { 830 units = append(units, newUnit(m.st, &pudoc)) 831 docs := []unitDoc{} 832 err = unitsCollection.Find(bson.D{{"principal", pudoc.Name}}).All(&docs) 833 if err != nil { 834 return nil, err 835 } 836 for _, doc := range docs { 837 units = append(units, newUnit(m.st, &doc)) 838 } 839 } 840 return units, nil 841 } 842 843 // SetProvisioned sets the provider specific machine id, nonce and also metadata for 844 // this machine. Once set, the instance id cannot be changed. 845 // 846 // When provisioning an instance, a nonce should be created and passed 847 // when starting it, before adding the machine to the state. This means 848 // that if the provisioner crashes (or its connection to the state is 849 // lost) after starting the instance, we can be sure that only a single 850 // instance will be able to act for that machine. 851 func (m *Machine) SetProvisioned(id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics) (err error) { 852 defer errors.DeferredAnnotatef(&err, "cannot set instance data for machine %q", m) 853 854 if id == "" || nonce == "" { 855 return fmt.Errorf("instance id and nonce cannot be empty") 856 } 857 858 if characteristics == nil { 859 characteristics = &instance.HardwareCharacteristics{} 860 } 861 instData := &instanceData{ 862 DocID: m.doc.DocID, 863 MachineId: m.doc.Id, 864 InstanceId: id, 865 EnvUUID: m.doc.EnvUUID, 866 Arch: characteristics.Arch, 867 Mem: characteristics.Mem, 868 RootDisk: characteristics.RootDisk, 869 CpuCores: characteristics.CpuCores, 870 CpuPower: characteristics.CpuPower, 871 Tags: characteristics.Tags, 872 AvailZone: characteristics.AvailabilityZone, 873 } 874 875 ops := []txn.Op{ 876 { 877 C: machinesC, 878 Id: m.doc.DocID, 879 Assert: append(isAliveDoc, bson.DocElem{"nonce", ""}), 880 Update: bson.D{{"$set", bson.D{{"nonce", nonce}}}}, 881 }, { 882 C: instanceDataC, 883 Id: m.doc.DocID, 884 Assert: txn.DocMissing, 885 Insert: instData, 886 }, 887 } 888 889 if err = m.st.runTransaction(ops); err == nil { 890 m.doc.Nonce = nonce 891 return nil 892 } else if err != txn.ErrAborted { 893 return err 894 } else if alive, err := isAlive(m.st, machinesC, m.doc.DocID); err != nil { 895 return err 896 } else if !alive { 897 return errNotAlive 898 } 899 return fmt.Errorf("already set") 900 } 901 902 // SetInstanceInfo is used to provision a machine and in one steps set 903 // it's instance id, nonce, hardware characteristics, add networks and 904 // network interfaces as needed. 905 // 906 // TODO(dimitern) Do all the operations described in a single 907 // transaction, rather than using separate calls. Alternatively, 908 // we can add all the things to create/set in a document in some 909 // collection and have a worker that takes care of the actual work. 910 // Merge SetProvisioned() in here or drop it at that point. 911 func (m *Machine) SetInstanceInfo( 912 id instance.Id, nonce string, characteristics *instance.HardwareCharacteristics, 913 networks []NetworkInfo, interfaces []NetworkInterfaceInfo, 914 blockDevices map[string]BlockDeviceInfo) error { 915 916 // Add the networks and interfaces first. 917 for _, network := range networks { 918 _, err := m.st.AddNetwork(network) 919 if err != nil && errors.IsAlreadyExists(err) { 920 // Ignore already existing networks. 921 continue 922 } else if err != nil { 923 return errors.Trace(err) 924 } 925 } 926 for _, iface := range interfaces { 927 _, err := m.AddNetworkInterface(iface) 928 if err != nil && errors.IsAlreadyExists(err) { 929 // Ignore already existing network interfaces. 930 continue 931 } else if err != nil { 932 return errors.Trace(err) 933 } 934 } 935 if err := setProvisionedBlockDeviceInfo(m.st, m.Id(), blockDevices); err != nil { 936 return errors.Trace(err) 937 } 938 return m.SetProvisioned(id, nonce, characteristics) 939 } 940 941 func mergedAddresses(machineAddresses, providerAddresses []address) []network.Address { 942 merged := make([]network.Address, 0, len(providerAddresses)+len(machineAddresses)) 943 providerValues := set.NewStrings() 944 for _, address := range providerAddresses { 945 // Older versions of Juju may have stored an empty address so ignore it here. 946 if address.Value == "" || providerValues.Contains(address.Value) { 947 continue 948 } 949 providerValues.Add(address.Value) 950 merged = append(merged, address.InstanceAddress()) 951 } 952 for _, address := range machineAddresses { 953 if !providerValues.Contains(address.Value) { 954 merged = append(merged, address.InstanceAddress()) 955 } 956 } 957 return merged 958 } 959 960 // Addresses returns any hostnames and ips associated with a machine, 961 // determined both by the machine itself, and by asking the provider. 962 // 963 // The addresses returned by the provider shadow any of the addresses 964 // that the machine reported with the same address value. 965 // Provider-reported addresses always come before machine-reported 966 // addresses. Duplicates are removed. 967 func (m *Machine) Addresses() (addresses []network.Address) { 968 return mergedAddresses(m.doc.MachineAddresses, m.doc.Addresses) 969 } 970 971 // SetAddresses records any addresses related to the machine, sourced 972 // by asking the provider. 973 func (m *Machine) SetAddresses(addresses ...network.Address) (err error) { 974 if err = m.setAddresses(addresses, &m.doc.Addresses, "addresses"); err != nil { 975 return fmt.Errorf("cannot set addresses of machine %v: %v", m, err) 976 } 977 return nil 978 } 979 980 // MachineAddresses returns any hostnames and ips associated with a machine, 981 // determined by asking the machine itself. 982 func (m *Machine) MachineAddresses() (addresses []network.Address) { 983 for _, address := range m.doc.MachineAddresses { 984 addresses = append(addresses, address.InstanceAddress()) 985 } 986 return 987 } 988 989 // SetMachineAddresses records any addresses related to the machine, sourced 990 // by asking the machine. 991 func (m *Machine) SetMachineAddresses(addresses ...network.Address) (err error) { 992 if err = m.setAddresses(addresses, &m.doc.MachineAddresses, "machineaddresses"); err != nil { 993 return fmt.Errorf("cannot set machine addresses of machine %v: %v", m, err) 994 } 995 return nil 996 } 997 998 // setAddresses updates the machine's addresses (either Addresses or 999 // MachineAddresses, depending on the field argument). 1000 func (m *Machine) setAddresses(addresses []network.Address, field *[]address, fieldName string) error { 1001 var addressesToSet []network.Address 1002 if !m.IsContainer() { 1003 // Check addresses first. We'll only add those addresses 1004 // which are not in the IP address collection. 1005 ipAddresses, closer := m.st.getCollection(ipaddressesC) 1006 defer closer() 1007 1008 addressValues := make([]string, len(addresses)) 1009 for i, address := range addresses { 1010 addressValues[i] = address.Value 1011 } 1012 ipDocs := []ipaddressDoc{} 1013 sel := bson.D{{"value", bson.D{{"$in", addressValues}}}, {"state", AddressStateAllocated}} 1014 err := ipAddresses.Find(sel).All(&ipDocs) 1015 if err != nil { 1016 return err 1017 } 1018 ipDocValues := set.NewStrings() 1019 for _, ipDoc := range ipDocs { 1020 ipDocValues.Add(ipDoc.Value) 1021 } 1022 for _, address := range addresses { 1023 if !ipDocValues.Contains(address.Value) { 1024 addressesToSet = append(addressesToSet, address) 1025 } 1026 } 1027 } else { 1028 // Containers will set all addresses. 1029 addressesToSet = make([]network.Address, len(addresses)) 1030 copy(addressesToSet, addresses) 1031 } 1032 // Update addresses now. 1033 var changed bool 1034 envConfig, err := m.st.EnvironConfig() 1035 if err != nil { 1036 return err 1037 } 1038 1039 network.SortAddresses(addressesToSet, envConfig.PreferIPv6()) 1040 stateAddresses := instanceAddressesToAddresses(addressesToSet) 1041 buildTxn := func(attempt int) ([]txn.Op, error) { 1042 changed = false 1043 if attempt > 0 { 1044 if err := m.Refresh(); err != nil { 1045 return nil, err 1046 } 1047 } 1048 if m.doc.Life == Dead { 1049 return nil, ErrDead 1050 } 1051 op := txn.Op{ 1052 C: machinesC, 1053 Id: m.doc.DocID, 1054 Assert: append(bson.D{{fieldName, *field}}, notDeadDoc...), 1055 } 1056 if !addressesEqual(addressesToSet, addressesToInstanceAddresses(*field)) { 1057 op.Update = bson.D{{"$set", bson.D{{fieldName, stateAddresses}}}} 1058 changed = true 1059 } 1060 return []txn.Op{op}, nil 1061 } 1062 switch err := m.st.run(buildTxn); err { 1063 case nil: 1064 case jujutxn.ErrExcessiveContention: 1065 return errors.Annotatef(err, "cannot set %s for machine %s", fieldName, m) 1066 default: 1067 return err 1068 } 1069 if !changed { 1070 return nil 1071 } 1072 *field = stateAddresses 1073 return nil 1074 } 1075 1076 // RequestedNetworks returns the list of network names the machine 1077 // should be on. Unlike networks specified with constraints, these 1078 // networks are required to be present on the machine. 1079 func (m *Machine) RequestedNetworks() ([]string, error) { 1080 return readRequestedNetworks(m.st, m.globalKey()) 1081 } 1082 1083 // Networks returns the list of configured networks on the machine. 1084 // The configured and requested networks on a machine must match. 1085 func (m *Machine) Networks() ([]*Network, error) { 1086 requestedNetworks, err := m.RequestedNetworks() 1087 if err != nil { 1088 return nil, err 1089 } 1090 docs := []networkDoc{} 1091 1092 networksCollection, closer := m.st.getCollection(networksC) 1093 defer closer() 1094 1095 sel := bson.D{{"name", bson.D{{"$in", requestedNetworks}}}} 1096 err = networksCollection.Find(sel).All(&docs) 1097 if err != nil { 1098 return nil, err 1099 } 1100 networks := make([]*Network, len(docs)) 1101 for i, doc := range docs { 1102 networks[i] = newNetwork(m.st, &doc) 1103 } 1104 return networks, nil 1105 } 1106 1107 // NetworkInterfaces returns the list of configured network interfaces 1108 // of the machine. 1109 func (m *Machine) NetworkInterfaces() ([]*NetworkInterface, error) { 1110 networkInterfaces, closer := m.st.getCollection(networkInterfacesC) 1111 defer closer() 1112 1113 docs := []networkInterfaceDoc{} 1114 err := networkInterfaces.Find(bson.D{{"machineid", m.doc.Id}}).All(&docs) 1115 if err != nil { 1116 return nil, err 1117 } 1118 ifaces := make([]*NetworkInterface, len(docs)) 1119 for i, doc := range docs { 1120 ifaces[i] = newNetworkInterface(m.st, &doc) 1121 } 1122 return ifaces, nil 1123 } 1124 1125 // AddNetworkInterface creates a new network interface with the given 1126 // args for this machine. The machine must be alive and not yet 1127 // provisioned, and there must be no other interface with the same MAC 1128 // address on the same network, or the same name on that machine for 1129 // this to succeed. If a network interface already exists, the 1130 // returned error satisfies errors.IsAlreadyExists. 1131 func (m *Machine) AddNetworkInterface(args NetworkInterfaceInfo) (iface *NetworkInterface, err error) { 1132 defer errors.DeferredAnnotatef(&err, "cannot add network interface %q to machine %q", args.InterfaceName, m.doc.Id) 1133 1134 if args.MACAddress == "" { 1135 return nil, fmt.Errorf("MAC address must be not empty") 1136 } 1137 if _, err = net.ParseMAC(args.MACAddress); err != nil { 1138 return nil, err 1139 } 1140 if args.InterfaceName == "" { 1141 return nil, fmt.Errorf("interface name must be not empty") 1142 } 1143 doc := newNetworkInterfaceDoc(m.doc.Id, m.st.EnvironUUID(), args) 1144 ops := []txn.Op{{ 1145 C: networksC, 1146 Id: m.st.docID(args.NetworkName), 1147 Assert: txn.DocExists, 1148 }, { 1149 C: machinesC, 1150 Id: m.doc.DocID, 1151 Assert: isAliveDoc, 1152 }, { 1153 C: networkInterfacesC, 1154 Id: doc.Id, 1155 Assert: txn.DocMissing, 1156 Insert: doc, 1157 }} 1158 1159 err = m.st.runTransaction(ops) 1160 switch err { 1161 case txn.ErrAborted: 1162 if _, err = m.st.Network(args.NetworkName); err != nil { 1163 return nil, err 1164 } 1165 if err = m.Refresh(); err != nil { 1166 return nil, err 1167 } else if m.doc.Life != Alive { 1168 return nil, fmt.Errorf("machine is not alive") 1169 } 1170 // Should never happen. 1171 logger.Errorf("unhandled assert while adding network interface doc %#v", doc) 1172 case nil: 1173 // We have a unique key restrictions on the following fields: 1174 // - InterfaceName, MachineId 1175 // - MACAddress, NetworkName 1176 // These will cause the insert to fail if there is another record 1177 // with the same combination of values in the table. 1178 // The txn logic does not report insertion errors, so we check 1179 // that the record has actually been inserted correctly before 1180 // reporting success. 1181 networkInterfaces, closer := m.st.getCollection(networkInterfacesC) 1182 defer closer() 1183 1184 if err = networkInterfaces.FindId(doc.Id).One(&doc); err == nil { 1185 return newNetworkInterface(m.st, doc), nil 1186 } 1187 sel := bson.D{{"interfacename", args.InterfaceName}, {"machineid", m.doc.Id}} 1188 if err = networkInterfaces.Find(sel).One(nil); err == nil { 1189 return nil, errors.AlreadyExistsf("%q on machine %q", args.InterfaceName, m.doc.Id) 1190 } 1191 sel = bson.D{{"macaddress", args.MACAddress}, {"networkname", args.NetworkName}} 1192 if err = networkInterfaces.Find(sel).One(nil); err == nil { 1193 return nil, errors.AlreadyExistsf("MAC address %q on network %q", args.MACAddress, args.NetworkName) 1194 } 1195 // Should never happen. 1196 logger.Errorf("unknown error while adding network interface doc %#v", doc) 1197 } 1198 return nil, err 1199 } 1200 1201 // CheckProvisioned returns true if the machine was provisioned with the given nonce. 1202 func (m *Machine) CheckProvisioned(nonce string) bool { 1203 return nonce == m.doc.Nonce && nonce != "" 1204 } 1205 1206 // String returns a unique description of this machine. 1207 func (m *Machine) String() string { 1208 return m.doc.Id 1209 } 1210 1211 // Placement returns the machine's Placement structure that should be used when 1212 // provisioning an instance for the machine. 1213 func (m *Machine) Placement() string { 1214 return m.doc.Placement 1215 } 1216 1217 // Constraints returns the exact constraints that should apply when provisioning 1218 // an instance for the machine. 1219 func (m *Machine) Constraints() (constraints.Value, error) { 1220 return readConstraints(m.st, m.globalKey()) 1221 } 1222 1223 // SetConstraints sets the exact constraints to apply when provisioning an 1224 // instance for the machine. It will fail if the machine is Dead, or if it 1225 // is already provisioned. 1226 func (m *Machine) SetConstraints(cons constraints.Value) (err error) { 1227 defer errors.DeferredAnnotatef(&err, "cannot set constraints") 1228 unsupported, err := m.st.validateConstraints(cons) 1229 if len(unsupported) > 0 { 1230 logger.Warningf( 1231 "setting constraints on machine %q: unsupported constraints: %v", m.Id(), strings.Join(unsupported, ",")) 1232 } else if err != nil { 1233 return err 1234 } 1235 notSetYet := bson.D{{"nonce", ""}} 1236 ops := []txn.Op{ 1237 { 1238 C: machinesC, 1239 Id: m.doc.DocID, 1240 Assert: append(isAliveDoc, notSetYet...), 1241 }, 1242 setConstraintsOp(m.st, m.globalKey(), cons), 1243 } 1244 // make multiple attempts to push the ErrExcessiveContention case out of the 1245 // realm of plausibility: it implies local state indicating unprovisioned, 1246 // and remote state indicating provisioned (reasonable); but which changes 1247 // back to unprovisioned and then to provisioned again with *very* specific 1248 // timing in the course of this loop. 1249 buildTxn := func(attempt int) ([]txn.Op, error) { 1250 if attempt > 0 { 1251 if m, err = m.st.Machine(m.doc.Id); err != nil { 1252 return nil, err 1253 } 1254 } 1255 if m.doc.Life != Alive { 1256 return nil, errNotAlive 1257 } 1258 if _, err := m.InstanceId(); err == nil { 1259 return nil, fmt.Errorf("machine is already provisioned") 1260 } else if !errors.IsNotProvisioned(err) { 1261 return nil, err 1262 } 1263 return ops, nil 1264 } 1265 return m.st.run(buildTxn) 1266 } 1267 1268 // Status returns the status of the machine. 1269 func (m *Machine) Status() (status Status, info string, data map[string]interface{}, err error) { 1270 doc, err := getStatus(m.st, m.globalKey()) 1271 if err != nil { 1272 return "", "", nil, err 1273 } 1274 status = doc.Status 1275 info = doc.StatusInfo 1276 data = doc.StatusData 1277 return 1278 } 1279 1280 // SetStatus sets the status of the machine. 1281 func (m *Machine) SetStatus(status Status, info string, data map[string]interface{}) error { 1282 // If a machine is not yet provisioned, we allow its status 1283 // to be set back to pending (when a retry is to occur). 1284 _, err := m.InstanceId() 1285 allowPending := errors.IsNotProvisioned(err) 1286 doc, err := newMachineStatusDoc(status, info, data, allowPending) 1287 if err != nil { 1288 return err 1289 } 1290 ops := []txn.Op{{ 1291 C: machinesC, 1292 Id: m.doc.DocID, 1293 Assert: notDeadDoc, 1294 }, 1295 updateStatusOp(m.st, m.globalKey(), doc.statusDoc), 1296 } 1297 if err = m.st.runTransaction(ops); err != nil { 1298 return fmt.Errorf("cannot set status of machine %q: %v", m, onAbort(err, errNotAlive)) 1299 } 1300 return nil 1301 } 1302 1303 // Clean returns true if the machine does not have any deployed units or containers. 1304 func (m *Machine) Clean() bool { 1305 return m.doc.Clean 1306 } 1307 1308 // SupportedContainers returns any containers this machine is capable of hosting, and a bool 1309 // indicating if the supported containers have been determined or not. 1310 func (m *Machine) SupportedContainers() ([]instance.ContainerType, bool) { 1311 return m.doc.SupportedContainers, m.doc.SupportedContainersKnown 1312 } 1313 1314 // SupportsNoContainers records the fact that this machine doesn't support any containers. 1315 func (m *Machine) SupportsNoContainers() (err error) { 1316 if err = m.updateSupportedContainers([]instance.ContainerType{}); err != nil { 1317 return err 1318 } 1319 return m.markInvalidContainers() 1320 } 1321 1322 // SetSupportedContainers sets the list of containers supported by this machine. 1323 func (m *Machine) SetSupportedContainers(containers []instance.ContainerType) (err error) { 1324 if len(containers) == 0 { 1325 return fmt.Errorf("at least one valid container type is required") 1326 } 1327 for _, container := range containers { 1328 if container == instance.NONE { 1329 return fmt.Errorf("%q is not a valid container type", container) 1330 } 1331 } 1332 if err = m.updateSupportedContainers(containers); err != nil { 1333 return err 1334 } 1335 return m.markInvalidContainers() 1336 } 1337 1338 func isSupportedContainer(container instance.ContainerType, supportedContainers []instance.ContainerType) bool { 1339 for _, supportedContainer := range supportedContainers { 1340 if supportedContainer == container { 1341 return true 1342 } 1343 } 1344 return false 1345 } 1346 1347 // updateSupportedContainers sets the supported containers on this host machine. 1348 func (m *Machine) updateSupportedContainers(supportedContainers []instance.ContainerType) (err error) { 1349 ops := []txn.Op{ 1350 { 1351 C: machinesC, 1352 Id: m.doc.DocID, 1353 Assert: notDeadDoc, 1354 Update: bson.D{ 1355 {"$set", bson.D{ 1356 {"supportedcontainers", supportedContainers}, 1357 {"supportedcontainersknown", true}, 1358 }}}, 1359 }, 1360 } 1361 if err = m.st.runTransaction(ops); err != nil { 1362 err = onAbort(err, ErrDead) 1363 logger.Errorf("cannot update supported containers of machine %v: %v", m, err) 1364 return err 1365 } 1366 m.doc.SupportedContainers = supportedContainers 1367 m.doc.SupportedContainersKnown = true 1368 return nil 1369 } 1370 1371 // markInvalidContainers sets the status of any container belonging to this machine 1372 // as being in error if the container type is not supported. 1373 func (m *Machine) markInvalidContainers() error { 1374 currentContainers, err := m.Containers() 1375 if err != nil { 1376 return err 1377 } 1378 for _, containerId := range currentContainers { 1379 if !isSupportedContainer(ContainerTypeFromId(containerId), m.doc.SupportedContainers) { 1380 container, err := m.st.Machine(containerId) 1381 if err != nil { 1382 logger.Errorf("loading container %v to mark as invalid: %v", containerId, err) 1383 continue 1384 } 1385 // There should never be a circumstance where an unsupported container is started. 1386 // Nonetheless, we check and log an error if such a situation arises. 1387 status, _, _, err := container.Status() 1388 if err != nil { 1389 logger.Errorf("finding status of container %v to mark as invalid: %v", containerId, err) 1390 continue 1391 } 1392 if status == StatusPending { 1393 containerType := ContainerTypeFromId(containerId) 1394 container.SetStatus( 1395 StatusError, "unsupported container", map[string]interface{}{"type": containerType}) 1396 } else { 1397 logger.Errorf("unsupported container %v has unexpected status %v", containerId, status) 1398 } 1399 } 1400 } 1401 return nil 1402 } 1403 1404 // SetMachineBlockDevices sets the block devices visible on the machine. 1405 func (m *Machine) SetMachineBlockDevices(info ...BlockDeviceInfo) error { 1406 return setMachineBlockDevices(m.st, m.Id(), info) 1407 } 1408 1409 // BlockDevices gets the aggregated list of block devices associated with 1410 // the machine, including unprovisioned ones. 1411 func (m *Machine) BlockDevices() ([]BlockDevice, error) { 1412 devices, err := getMachineBlockDevices(m.st, m.Id()) 1413 if err != nil { 1414 return nil, err 1415 } 1416 result := make([]BlockDevice, len(devices)) 1417 for i, dev := range devices { 1418 result[i] = dev 1419 } 1420 return result, nil 1421 }