launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/state/unit.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 errgo "launchpad.net/errgo/errors" 9 "time" 10 11 "github.com/loggo/loggo" 12 "labix.org/v2/mgo" 13 "labix.org/v2/mgo/bson" 14 "labix.org/v2/mgo/txn" 15 16 "launchpad.net/juju-core/charm" 17 "launchpad.net/juju-core/constraints" 18 "launchpad.net/juju-core/errors" 19 "launchpad.net/juju-core/instance" 20 "launchpad.net/juju-core/names" 21 "launchpad.net/juju-core/state/api/params" 22 "launchpad.net/juju-core/state/presence" 23 "launchpad.net/juju-core/tools" 24 "launchpad.net/juju-core/utils" 25 "launchpad.net/juju-core/version" 26 ) 27 28 var unitLogger = loggo.GetLogger("juju.state.unit") 29 30 // AssignmentPolicy controls what machine a unit will be assigned to. 31 type AssignmentPolicy string 32 33 const ( 34 // AssignLocal indicates that all service units should be assigned 35 // to machine 0. 36 AssignLocal AssignmentPolicy = "local" 37 38 // AssignClean indicates that every service unit should be assigned 39 // to a machine which never previously has hosted any units, and that 40 // new machines should be launched if required. 41 AssignClean AssignmentPolicy = "clean" 42 43 // AssignCleanEmpty indicates that every service unit should be assigned 44 // to a machine which never previously has hosted any units, and which is not 45 // currently hosting any containers, and that new machines should be launched if required. 46 AssignCleanEmpty AssignmentPolicy = "clean-empty" 47 48 // AssignNew indicates that every service unit should be assigned to a new 49 // dedicated machine. A new machine will be launched for each new unit. 50 AssignNew AssignmentPolicy = "new" 51 ) 52 53 // ResolvedMode describes the way state transition errors 54 // are resolved. 55 type ResolvedMode string 56 57 const ( 58 ResolvedNone ResolvedMode = "" 59 ResolvedRetryHooks ResolvedMode = "retry-hooks" 60 ResolvedNoHooks ResolvedMode = "no-hooks" 61 ) 62 63 // unitDoc represents the internal state of a unit in MongoDB. 64 // Note the correspondence with UnitInfo in state/api/params. 65 type unitDoc struct { 66 Name string `bson:"_id"` 67 Service string 68 Series string 69 CharmURL *charm.URL 70 Principal string 71 Subordinates []string 72 PublicAddress string 73 PrivateAddress string 74 MachineId string 75 Resolved ResolvedMode 76 Tools *tools.Tools `bson:",omitempty"` 77 Ports []instance.Port 78 Life Life 79 TxnRevno int64 `bson:"txn-revno"` 80 PasswordHash string 81 } 82 83 // Unit represents the state of a service unit. 84 type Unit struct { 85 st *State 86 doc unitDoc 87 annotator 88 } 89 90 func newUnit(st *State, udoc *unitDoc) *Unit { 91 unit := &Unit{ 92 st: st, 93 doc: *udoc, 94 } 95 unit.annotator = annotator{ 96 globalKey: unit.globalKey(), 97 tag: unit.Tag(), 98 st: st, 99 } 100 return unit 101 } 102 103 // Service returns the service. 104 func (u *Unit) Service() (*Service, error) { 105 return u.st.Service(u.doc.Service) 106 } 107 108 // ConfigSettings returns the complete set of service charm config settings 109 // available to the unit. Unset values will be replaced with the default 110 // value for the associated option, and may thus be nil when no default is 111 // specified. 112 func (u *Unit) ConfigSettings() (charm.Settings, error) { 113 if u.doc.CharmURL == nil { 114 return nil, errgo.Newf("unit charm not set") 115 } 116 settings, err := readSettings(u.st, serviceSettingsKey(u.doc.Service, u.doc.CharmURL)) 117 if err != nil { 118 return nil, mask(err) 119 } 120 charm, err := u.st.Charm(u.doc.CharmURL) 121 if err != nil { 122 return nil, mask(err) 123 } 124 result := charm.Config().DefaultSettings() 125 for name, value := range settings.Map() { 126 result[name] = value 127 } 128 return result, nil 129 } 130 131 // ServiceName returns the service name. 132 func (u *Unit) ServiceName() string { 133 return u.doc.Service 134 } 135 136 // Series returns the deployed charm's series. 137 func (u *Unit) Series() string { 138 return u.doc.Series 139 } 140 141 // String returns the unit as string. 142 func (u *Unit) String() string { 143 return u.doc.Name 144 } 145 146 // Name returns the unit name. 147 func (u *Unit) Name() string { 148 return u.doc.Name 149 } 150 151 // unitGlobalKey returns the global database key for the named unit. 152 func unitGlobalKey(name string) string { 153 return "u#" + name 154 } 155 156 // globalKey returns the global database key for the unit. 157 func (u *Unit) globalKey() string { 158 return unitGlobalKey(u.doc.Name) 159 } 160 161 // Life returns whether the unit is Alive, Dying or Dead. 162 func (u *Unit) Life() Life { 163 return u.doc.Life 164 } 165 166 // AgentTools returns the tools that the agent is currently running. 167 // It an error that satisfies IsNotFound if the tools have not yet been set. 168 func (u *Unit) AgentTools() (*tools.Tools, error) { 169 if u.doc.Tools == nil { 170 return nil, errors.NotFoundf("agent tools for unit %q", u) 171 } 172 tools := *u.doc.Tools 173 return &tools, nil 174 } 175 176 // SetAgentVersion sets the version of juju that the agent is 177 // currently running. 178 func (u *Unit) SetAgentVersion(v version.Binary) (err error) { 179 defer utils.ErrorContextf(&err, "cannot set agent version for unit %q", u) 180 if err = checkVersionValidity(v); err != nil { 181 return mask(err) 182 } 183 tools := &tools.Tools{Version: v} 184 ops := []txn.Op{{ 185 C: u.st.units.Name, 186 Id: u.doc.Name, 187 Assert: notDeadDoc, 188 Update: D{{"$set", D{{"tools", tools}}}}, 189 }} 190 if err := u.st.runTransaction(ops); err != nil { 191 return onAbort(err, errDead) 192 } 193 u.doc.Tools = tools 194 return nil 195 } 196 197 // SetMongoPassword sets the password the agent responsible for the unit 198 // should use to communicate with the state servers. Previous passwords 199 // are invalidated. 200 func (u *Unit) SetMongoPassword(password string) error { 201 return u.st.setMongoPassword(u.Tag(), password) 202 } 203 204 // SetPassword sets the password for the machine's agent. 205 func (u *Unit) SetPassword(password string) error { 206 if len(password) < utils.MinAgentPasswordLength { 207 return errgo.Newf("password is only %d bytes long, and is not a valid Agent password", len(password)) 208 } 209 return u.setPasswordHash(utils.AgentPasswordHash(password)) 210 } 211 212 // setPasswordHash sets the underlying password hash in the database directly 213 // to the value supplied. This is split out from SetPassword to allow direct 214 // manipulation in tests (to check for backwards compatibility). 215 func (u *Unit) setPasswordHash(passwordHash string) error { 216 ops := []txn.Op{{ 217 C: u.st.units.Name, 218 Id: u.doc.Name, 219 Assert: notDeadDoc, 220 Update: D{{"$set", D{{"passwordhash", passwordHash}}}}, 221 }} 222 err := u.st.runTransaction(ops) 223 if err != nil { 224 return errgo.Newf("cannot set password of unit %q: %v", u, onAbort(err, errDead)) 225 } 226 u.doc.PasswordHash = passwordHash 227 return nil 228 } 229 230 // Return the underlying PasswordHash stored in the database. Used by the test 231 // suite to check that the PasswordHash gets properly updated to new values 232 // when compatibility mode is detected. 233 func (u *Unit) getPasswordHash() string { 234 return u.doc.PasswordHash 235 } 236 237 // PasswordValid returns whether the given password is valid 238 // for the given unit. 239 func (u *Unit) PasswordValid(password string) bool { 240 agentHash := utils.AgentPasswordHash(password) 241 if agentHash == u.doc.PasswordHash { 242 return true 243 } 244 // In Juju 1.16 and older we used the slower password hash for unit 245 // agents. So check to see if the supplied password matches the old 246 // path, and if so, update it to the new mechanism. 247 // We ignore any error in setting the password hash, as we'll just try 248 // again next time 249 if utils.UserPasswordHash(password, utils.CompatSalt) == u.doc.PasswordHash { 250 logger.Debugf("%s logged in with old password hash, changing to AgentPasswordHash", 251 u.Tag()) 252 u.setPasswordHash(agentHash) 253 return true 254 } 255 return false 256 } 257 258 // Destroy, when called on a Alive unit, advances its lifecycle as far as 259 // possible; it otherwise has no effect. In most situations, the unit's 260 // life is just set to Dying; but if a principal unit that is not assigned 261 // to a provisioned machine is Destroyed, it will be removed from state 262 // directly. 263 func (u *Unit) Destroy() (err error) { 264 defer func() { 265 if err == nil { 266 // This is a white lie; the document might actually be removed. 267 u.doc.Life = Dying 268 } 269 }() 270 unit := &Unit{st: u.st, doc: u.doc} 271 for i := 0; i < 5; i++ { 272 switch ops, err := unit.destroyOps(); err { 273 case errRefresh: 274 case errAlreadyDying: 275 return nil 276 case nil: 277 if err := unit.st.runTransaction(ops); err != txn.ErrAborted { 278 return mask(err) 279 } 280 default: 281 return err 282 } 283 if err := unit.Refresh(); errors.IsNotFoundError(err) { 284 return nil 285 } else if err != nil { 286 return mask(err) 287 } 288 } 289 return ErrExcessiveContention 290 } 291 292 // destroyOps returns the operations required to destroy the unit. If it 293 // returns errRefresh, the unit should be refreshed and the destruction 294 // operations recalculated. 295 func (u *Unit) destroyOps() ([]txn.Op, error) { 296 if u.doc.Life != Alive { 297 return nil, errAlreadyDying 298 } 299 300 // Where possible, we'd like to be able to short-circuit unit destruction 301 // such that units can be removed directly rather than waiting for their 302 // agents to start, observe Dying, set Dead, and shut down; this takes a 303 // long time and is vexing to users. This turns out to be possible if and 304 // only if the unit agent has not yet set its status; this implies that the 305 // most the unit could possibly have done is to run its install hook. 306 // 307 // There's no harm in removing a unit that's run its install hook only -- 308 // or, at least, there is no more harm than there is in removing a unit 309 // that's run its stop hook, and that's the usual condition. 310 // 311 // Principals with subordinates are never eligible for this shortcut, 312 // because the unit agent must inevitably have set a status before getting 313 // to the point where it can actually create its subordinate. 314 // 315 // Subordinates should be eligible for the shortcut but are not currently 316 // considered, on the basis that (1) they were created by active principals 317 // and can be expected to be deployed pretty soon afterwards, so we don't 318 // lose much time and (2) by maintaining this restriction, I can reduce 319 // the number of tests that have to change and defer that improvement to 320 // its own CL. 321 minUnitsOp := minUnitsTriggerOp(u.st, u.ServiceName()) 322 setDyingOps := []txn.Op{{ 323 C: u.st.units.Name, 324 Id: u.doc.Name, 325 Assert: isAliveDoc, 326 Update: D{{"$set", D{{"life", Dying}}}}, 327 }, minUnitsOp} 328 if u.doc.Principal != "" { 329 return setDyingOps, nil 330 } else if len(u.doc.Subordinates) != 0 { 331 return setDyingOps, nil 332 } 333 334 sdocId := u.globalKey() 335 sdoc, err := getStatus(u.st, sdocId) 336 if errors.IsNotFoundError(err) { 337 return nil, errAlreadyDying 338 } else if err != nil { 339 return nil, mask(err) 340 } 341 if sdoc.Status != params.StatusPending { 342 return setDyingOps, nil 343 } 344 ops := []txn.Op{{ 345 C: u.st.statuses.Name, 346 Id: sdocId, 347 Assert: D{{"status", params.StatusPending}}, 348 }, minUnitsOp} 349 removeAsserts := append(isAliveDoc, unitHasNoSubordinates...) 350 removeOps, err := u.removeOps(removeAsserts) 351 if errgo.Cause(err) == errAlreadyRemoved { 352 return nil, errAlreadyDying 353 } else if err != nil { 354 return nil, mask(err) 355 } 356 return append(ops, removeOps...), nil 357 } 358 359 var errAlreadyRemoved = errgo.New("entity has already been removed") 360 361 // removeOps returns the operations necessary to remove the unit, assuming 362 // the supplied asserts apply to the unit document. 363 func (u *Unit) removeOps(asserts D) ([]txn.Op, error) { 364 svc, err := u.st.Service(u.doc.Service) 365 if errors.IsNotFoundError(err) { 366 // If the service has been removed, the unit must already have been. 367 return nil, errAlreadyRemoved 368 } else if err != nil { 369 return nil, mask(err) 370 } 371 return svc.removeUnitOps(u, asserts) 372 } 373 374 var ErrUnitHasSubordinates = errgo.New("unit has subordinates") 375 376 var unitHasNoSubordinates = D{{ 377 "$or", []D{ 378 {{"subordinates", D{{"$size", 0}}}}, 379 {{"subordinates", D{{"$exists", false}}}}, 380 }, 381 }} 382 383 // EnsureDead sets the unit lifecycle to Dead if it is Alive or Dying. 384 // It does nothing otherwise. If the unit has subordinates, it will 385 // return ErrUnitHasSubordinates. 386 func (u *Unit) EnsureDead() (err error) { 387 if u.doc.Life == Dead { 388 return nil 389 } 390 defer func() { 391 if err == nil { 392 u.doc.Life = Dead 393 } 394 }() 395 ops := []txn.Op{{ 396 C: u.st.units.Name, 397 Id: u.doc.Name, 398 Assert: append(notDeadDoc, unitHasNoSubordinates...), 399 Update: D{{"$set", D{{"life", Dead}}}}, 400 }} 401 if err := u.st.runTransaction(ops); err != txn.ErrAborted { 402 return mask(err) 403 } 404 if notDead, err := isNotDead(u.st.units, u.doc.Name); err != nil { 405 return mask(err) 406 } else if !notDead { 407 return nil 408 } 409 return ErrUnitHasSubordinates 410 } 411 412 // Remove removes the unit from state, and may remove its service as well, if 413 // the service is Dying and no other references to it exist. It will fail if 414 // the unit is not Dead. 415 func (u *Unit) Remove() (err error) { 416 defer utils.ErrorContextf(&err, "cannot remove unit %q", u) 417 if u.doc.Life != Dead { 418 return errgo.New("unit is not dead") 419 } 420 421 // Now the unit is Dead, we can be sure that it's impossible for it to 422 // enter relation scopes (once it's Dying, we can be sure of this; but 423 // EnsureDead does not require that it already be Dying, so this is the 424 // only point at which we can safely backstop lp:1233457 and mitigate 425 // the impact of unit agent bugs that leave relation scopes occupied). 426 relations, err := serviceRelations(u.st, u.doc.Service) 427 if err != nil { 428 return mask(err) 429 } 430 for _, rel := range relations { 431 ru, err := rel.Unit(u) 432 if err != nil { 433 return mask(err) 434 } 435 if err := ru.LeaveScope(); err != nil { 436 return mask(err) 437 } 438 } 439 440 // Now we're sure we haven't left any scopes occupied by this unit, we 441 // can safely remove the document. 442 unit := &Unit{st: u.st, doc: u.doc} 443 for i := 0; i < 5; i++ { 444 switch ops, err := unit.removeOps(isDeadDoc); err { 445 case errRefresh: 446 case errAlreadyRemoved: 447 return nil 448 case nil: 449 if err := u.st.runTransaction(ops); err != txn.ErrAborted { 450 return mask(err) 451 } 452 default: 453 return err 454 } 455 if err := unit.Refresh(); errors.IsNotFoundError(err) { 456 return nil 457 } else if err != nil { 458 return mask(err) 459 } 460 } 461 return ErrExcessiveContention 462 } 463 464 // Resolved returns the resolved mode for the unit. 465 func (u *Unit) Resolved() ResolvedMode { 466 return u.doc.Resolved 467 } 468 469 // IsPrincipal returns whether the unit is deployed in its own container, 470 // and can therefore have subordinate services deployed alongside it. 471 func (u *Unit) IsPrincipal() bool { 472 return u.doc.Principal == "" 473 } 474 475 // SubordinateNames returns the names of any subordinate units. 476 func (u *Unit) SubordinateNames() []string { 477 names := make([]string, len(u.doc.Subordinates)) 478 copy(names, u.doc.Subordinates) 479 return names 480 } 481 482 // DeployerTag returns the tag of the agent responsible for deploying 483 // the unit. If no such entity can be determined, false is returned. 484 func (u *Unit) DeployerTag() (string, bool) { 485 if u.doc.Principal != "" { 486 return names.UnitTag(u.doc.Principal), true 487 } else if u.doc.MachineId != "" { 488 return names.MachineTag(u.doc.MachineId), true 489 } 490 return "", false 491 } 492 493 // PrincipalName returns the name of the unit's principal. 494 // If the unit is not a subordinate, false is returned. 495 func (u *Unit) PrincipalName() (string, bool) { 496 return u.doc.Principal, u.doc.Principal != "" 497 } 498 499 // addressesOfMachine returns Addresses of the related machine if present. 500 func (u *Unit) addressesOfMachine() []instance.Address { 501 id := u.doc.MachineId 502 if id != "" { 503 m, err := u.st.Machine(id) 504 if err == nil { 505 return m.Addresses() 506 } 507 unitLogger.Errorf("unit %v misses machine id %v", u, id) 508 } 509 return nil 510 } 511 512 // PublicAddress returns the public address of the unit and whether it is valid. 513 func (u *Unit) PublicAddress() (string, bool) { 514 publicAddress := u.doc.PublicAddress 515 addresses := u.addressesOfMachine() 516 if len(addresses) > 0 { 517 publicAddress = instance.SelectPublicAddress(addresses) 518 } 519 return publicAddress, publicAddress != "" 520 } 521 522 // PrivateAddress returns the private address of the unit and whether it is valid. 523 func (u *Unit) PrivateAddress() (string, bool) { 524 privateAddress := u.doc.PrivateAddress 525 addresses := u.addressesOfMachine() 526 if len(addresses) > 0 { 527 privateAddress = instance.SelectInternalAddress(addresses, false) 528 } 529 return privateAddress, privateAddress != "" 530 } 531 532 // Refresh refreshes the contents of the Unit from the underlying 533 // state. It an error that satisfies IsNotFound if the unit has been removed. 534 func (u *Unit) Refresh() error { 535 err := u.st.units.FindId(u.doc.Name).One(&u.doc) 536 if errgo.Cause(err) == mgo.ErrNotFound { 537 return errors.NotFoundf("unit %q", u) 538 } 539 if err != nil { 540 return errgo.Notef(err, "cannot refresh unit %q", u) 541 } 542 return nil 543 } 544 545 // Status returns the status of the unit. 546 func (u *Unit) Status() (status params.Status, info string, data params.StatusData, err error) { 547 doc, err := getStatus(u.st, u.globalKey()) 548 if err != nil { 549 return "", "", nil, mask(err) 550 } 551 status = doc.Status 552 info = doc.StatusInfo 553 data = doc.StatusData 554 return 555 } 556 557 // SetStatus sets the status of the unit. The optional values 558 // allow to pass additional helpful status data. 559 func (u *Unit) SetStatus(status params.Status, info string, data params.StatusData) error { 560 doc := statusDoc{ 561 Status: status, 562 StatusInfo: info, 563 StatusData: data, 564 } 565 if err := doc.validateSet(); err != nil { 566 return mask(err) 567 } 568 ops := []txn.Op{{ 569 C: u.st.units.Name, 570 Id: u.doc.Name, 571 Assert: notDeadDoc, 572 }, 573 updateStatusOp(u.st, u.globalKey(), doc), 574 } 575 err := u.st.runTransaction(ops) 576 if err != nil { 577 return errgo.Newf("cannot set status of unit %q: %v", u, onAbort(err, errDead)) 578 } 579 return nil 580 } 581 582 // OpenPort sets the policy of the port with protocol and number to be opened. 583 func (u *Unit) OpenPort(protocol string, number int) (err error) { 584 port := instance.Port{Protocol: protocol, Number: number} 585 defer utils.ErrorContextf(&err, "cannot open port %v for unit %q", port, u) 586 ops := []txn.Op{{ 587 C: u.st.units.Name, 588 Id: u.doc.Name, 589 Assert: notDeadDoc, 590 Update: D{{"$addToSet", D{{"ports", port}}}}, 591 }} 592 err = u.st.runTransaction(ops) 593 if err != nil { 594 return onAbort(err, errDead) 595 } 596 found := false 597 for _, p := range u.doc.Ports { 598 if p == port { 599 break 600 } 601 } 602 if !found { 603 u.doc.Ports = append(u.doc.Ports, port) 604 } 605 return nil 606 } 607 608 // ClosePort sets the policy of the port with protocol and number to be closed. 609 func (u *Unit) ClosePort(protocol string, number int) (err error) { 610 port := instance.Port{Protocol: protocol, Number: number} 611 defer utils.ErrorContextf(&err, "cannot close port %v for unit %q", port, u) 612 ops := []txn.Op{{ 613 C: u.st.units.Name, 614 Id: u.doc.Name, 615 Assert: notDeadDoc, 616 Update: D{{"$pull", D{{"ports", port}}}}, 617 }} 618 err = u.st.runTransaction(ops) 619 if err != nil { 620 return onAbort(err, errDead) 621 } 622 newPorts := make([]instance.Port, 0, len(u.doc.Ports)) 623 for _, p := range u.doc.Ports { 624 if p != port { 625 newPorts = append(newPorts, p) 626 } 627 } 628 u.doc.Ports = newPorts 629 return nil 630 } 631 632 // OpenedPorts returns a slice containing the open ports of the unit. 633 func (u *Unit) OpenedPorts() []instance.Port { 634 ports := append([]instance.Port{}, u.doc.Ports...) 635 instance.SortPorts(ports) 636 return ports 637 } 638 639 // CharmURL returns the charm URL this unit is currently using. 640 func (u *Unit) CharmURL() (*charm.URL, bool) { 641 if u.doc.CharmURL == nil { 642 return nil, false 643 } 644 return u.doc.CharmURL, true 645 } 646 647 // SetCharmURL marks the unit as currently using the supplied charm URL. 648 // An error will be returned if the unit is dead, or the charm URL not known. 649 func (u *Unit) SetCharmURL(curl *charm.URL) (err error) { 650 defer func() { 651 if err == nil { 652 u.doc.CharmURL = curl 653 } 654 }() 655 if curl == nil { 656 return errgo.Newf("cannot set nil charm url") 657 } 658 for i := 0; i < 5; i++ { 659 if notDead, err := isNotDead(u.st.units, u.doc.Name); err != nil { 660 return mask(err) 661 } else if !notDead { 662 return errgo.Newf("unit %q is dead", u) 663 } 664 sel := D{{"_id", u.doc.Name}, {"charmurl", curl}} 665 if count, err := u.st.units.Find(sel).Count(); err != nil { 666 return mask(err) 667 } else if count == 1 { 668 // Already set 669 return nil 670 } 671 if count, err := u.st.charms.FindId(curl).Count(); err != nil { 672 return mask(err) 673 } else if count < 1 { 674 return errgo.Newf("unknown charm url %q", curl) 675 } 676 677 // Add a reference to the service settings for the new charm. 678 incOp, err := settingsIncRefOp(u.st, u.doc.Service, curl, false) 679 if err != nil { 680 return mask(err) 681 } 682 683 // Set the new charm URL. 684 differentCharm := D{{"charmurl", D{{"$ne", curl}}}} 685 ops := []txn.Op{ 686 incOp, 687 { 688 C: u.st.units.Name, 689 Id: u.doc.Name, 690 Assert: append(notDeadDoc, differentCharm...), 691 Update: D{{"$set", D{{"charmurl", curl}}}}, 692 }} 693 if u.doc.CharmURL != nil { 694 // Drop the reference to the old charm. 695 decOps, err := settingsDecRefOps(u.st, u.doc.Service, u.doc.CharmURL) 696 if err != nil { 697 return mask(err) 698 } 699 ops = append(ops, decOps...) 700 } 701 if err := u.st.runTransaction(ops); err != txn.ErrAborted { 702 return mask(err) 703 } 704 } 705 return ErrExcessiveContention 706 } 707 708 // AgentAlive returns whether the respective remote agent is alive. 709 func (u *Unit) AgentAlive() (bool, error) { 710 return u.st.pwatcher.Alive(u.globalKey()) 711 } 712 713 // Tag returns a name identifying the unit that is safe to use 714 // as a file name. The returned name will be different from other 715 // Tag values returned by any other entities from the same state. 716 func (u *Unit) Tag() string { 717 return names.UnitTag(u.Name()) 718 } 719 720 // WaitAgentAlive blocks until the respective agent is alive. 721 func (u *Unit) WaitAgentAlive(timeout time.Duration) (err error) { 722 defer utils.ErrorContextf(&err, "waiting for agent of unit %q", u) 723 ch := make(chan presence.Change) 724 u.st.pwatcher.Watch(u.globalKey(), ch) 725 defer u.st.pwatcher.Unwatch(u.globalKey(), ch) 726 for i := 0; i < 2; i++ { 727 select { 728 case change := <-ch: 729 if change.Alive { 730 return nil 731 } 732 case <-time.After(timeout): 733 return errgo.Newf("still not alive after timeout") 734 case <-u.st.pwatcher.Dead(): 735 return u.st.pwatcher.Err() 736 } 737 } 738 panic(fmt.Sprintf("presence reported dead status twice in a row for unit %q", u)) 739 } 740 741 // SetAgentAlive signals that the agent for unit u is alive. 742 // It returns the started pinger. 743 func (u *Unit) SetAgentAlive() (*presence.Pinger, error) { 744 p := presence.NewPinger(u.st.presence, u.globalKey()) 745 err := p.Start() 746 if err != nil { 747 return nil, mask(err) 748 } 749 return p, nil 750 } 751 752 // NotAssignedError indicates that a unit is not assigned to a machine (and, in 753 // the case of subordinate units, that the unit's principal is not assigned). 754 type NotAssignedError struct{ Unit *Unit } 755 756 func (e *NotAssignedError) Error() string { 757 return fmt.Sprintf("unit %q is not assigned to a machine", e.Unit) 758 } 759 760 func IsNotAssigned(err error) bool { 761 _, ok := err.(*NotAssignedError) 762 return ok 763 } 764 765 // AssignedMachineId returns the id of the assigned machine. 766 func (u *Unit) AssignedMachineId() (id string, err error) { 767 if u.IsPrincipal() { 768 if u.doc.MachineId == "" { 769 return "", &NotAssignedError{u} 770 } 771 return u.doc.MachineId, nil 772 } 773 pudoc := unitDoc{} 774 err = u.st.units.Find(D{{"_id", u.doc.Principal}}).One(&pudoc) 775 if errgo.Cause(err) == mgo.ErrNotFound { 776 return "", errors.NotFoundf("principal unit %q of %q", u.doc.Principal, u) 777 } else if err != nil { 778 return "", mask(err) 779 } 780 if pudoc.MachineId == "" { 781 return "", &NotAssignedError{u} 782 } 783 return pudoc.MachineId, nil 784 } 785 786 var ( 787 machineNotAliveErr = errgo.New("machine is not alive") 788 machineNotCleanErr = errgo.New("machine is dirty") 789 unitNotAliveErr = errgo.New("unit is not alive") 790 alreadyAssignedErr = errgo.New("unit is already assigned to a machine") 791 inUseErr = errgo.New("machine is not unused") 792 ) 793 794 // assignToMachine is the internal version of AssignToMachine, 795 // also used by AssignToUnusedMachine. It returns specific errors 796 // in some cases: 797 // - machineNotAliveErr when the machine is not alive. 798 // - unitNotAliveErr when the unit is not alive. 799 // - alreadyAssignedErr when the unit has already been assigned 800 // - inUseErr when the machine already has a unit assigned (if unused is true) 801 func (u *Unit) assignToMachine(m *Machine, unused bool) (err error) { 802 if u.doc.Series != m.doc.Series { 803 return errgo.Newf("series does not match") 804 } 805 if u.doc.MachineId != "" { 806 if u.doc.MachineId != m.Id() { 807 return alreadyAssignedErr 808 } 809 return nil 810 } 811 if u.doc.Principal != "" { 812 return errgo.Newf("unit is a subordinate") 813 } 814 canHost := false 815 for _, j := range m.doc.Jobs { 816 if j == JobHostUnits { 817 canHost = true 818 break 819 } 820 } 821 if !canHost { 822 return errgo.Newf("machine %q cannot host units", m) 823 } 824 assert := append(isAliveDoc, D{ 825 {"$or", []D{ 826 {{"machineid", ""}}, 827 {{"machineid", m.Id()}}, 828 }}, 829 }...) 830 massert := isAliveDoc 831 if unused { 832 massert = append(massert, D{{"clean", D{{"$ne", false}}}}...) 833 } 834 ops := []txn.Op{{ 835 C: u.st.units.Name, 836 Id: u.doc.Name, 837 Assert: assert, 838 Update: D{{"$set", D{{"machineid", m.doc.Id}}}}, 839 }, { 840 C: u.st.machines.Name, 841 Id: m.doc.Id, 842 Assert: massert, 843 Update: D{{"$addToSet", D{{"principals", u.doc.Name}}}, {"$set", D{{"clean", false}}}}, 844 }} 845 err = u.st.runTransaction(ops) 846 if err == nil { 847 u.doc.MachineId = m.doc.Id 848 m.doc.Clean = false 849 return nil 850 } 851 if err != txn.ErrAborted { 852 return mask(err) 853 } 854 u0, err := u.st.Unit(u.Name()) 855 if err != nil { 856 return mask(err) 857 } 858 m0, err := u.st.Machine(m.Id()) 859 if err != nil { 860 return mask(err) 861 } 862 switch { 863 case u0.Life() != Alive: 864 return unitNotAliveErr 865 case m0.Life() != Alive: 866 return machineNotAliveErr 867 case u0.doc.MachineId != "" || !unused: 868 return alreadyAssignedErr 869 } 870 return inUseErr 871 } 872 873 func assignContextf(err *error, unit *Unit, target string) { 874 if *err != nil { 875 *err = errgo.Newf("cannot assign unit %q to %s: %v", unit, target, *err) 876 } 877 } 878 879 // AssignToMachine assigns this unit to a given machine. 880 func (u *Unit) AssignToMachine(m *Machine) (err error) { 881 defer assignContextf(&err, u, fmt.Sprintf("machine %s", m)) 882 return u.assignToMachine(m, false) 883 } 884 885 // assignToNewMachine assigns the unit to a machine created according to 886 // the supplied params, with the supplied constraints. 887 func (u *Unit) assignToNewMachine(template MachineTemplate, parentId string, containerType instance.ContainerType) error { 888 template.principals = []string{u.doc.Name} 889 template.Dirty = true 890 891 var ( 892 mdoc *machineDoc 893 ops []txn.Op 894 err error 895 ) 896 switch { 897 case parentId == "" && containerType == "": 898 mdoc, ops, err = u.st.addMachineOps(template) 899 case parentId == "": 900 if containerType == "" { 901 return errgo.Newf("assignToNewMachine called without container type (should never happen)") 902 } 903 // The new parent machine is clean and only hosts units, 904 // regardless of its child. 905 parentParams := template 906 parentParams.Jobs = []MachineJob{JobHostUnits} 907 mdoc, ops, err = u.st.addMachineInsideNewMachineOps(template, parentParams, containerType) 908 default: 909 // Container type is specified but no parent id. 910 mdoc, ops, err = u.st.addMachineInsideMachineOps(template, parentId, containerType) 911 } 912 if err != nil { 913 return mask(err) 914 } 915 916 // Ensure the host machine is really clean. 917 if parentId != "" { 918 ops = append(ops, txn.Op{ 919 C: u.st.machines.Name, 920 Id: parentId, 921 Assert: D{{"clean", true}}, 922 }, txn.Op{ 923 C: u.st.containerRefs.Name, 924 Id: parentId, 925 Assert: D{hasNoContainersTerm}, 926 }) 927 } 928 isUnassigned := D{{"machineid", ""}} 929 asserts := append(isAliveDoc, isUnassigned...) 930 ops = append(ops, txn.Op{ 931 C: u.st.units.Name, 932 Id: u.doc.Name, 933 Assert: asserts, 934 Update: D{{"$set", D{{"machineid", mdoc.Id}}}}, 935 }) 936 937 err = u.st.runTransaction(ops) 938 if err == nil { 939 u.doc.MachineId = mdoc.Id 940 return nil 941 } else if err != txn.ErrAborted { 942 return mask(err) 943 } 944 945 // If we assume that the machine ops will never give us an 946 // operation that would fail (because the machine id(s) that it 947 // chooses are unique), then the only reasons that the 948 // transaction could have been aborted are: 949 // * the unit is no longer alive 950 // * the unit has been assigned to a different machine 951 // * the parent machine we want to create a container on was 952 // clean but became dirty 953 unit, err := u.st.Unit(u.Name()) 954 if err != nil { 955 return mask(err) 956 } 957 switch { 958 case unit.Life() != Alive: 959 return unitNotAliveErr 960 case unit.doc.MachineId != "": 961 return alreadyAssignedErr 962 } 963 if parentId == "" { 964 return errgo.Newf("cannot add top level machine: transaction aborted for unknown reason") 965 } 966 m, err := u.st.Machine(parentId) 967 if err != nil { 968 return mask(err) 969 } 970 if !m.Clean() { 971 return machineNotCleanErr 972 } 973 containers, err := m.Containers() 974 if err != nil { 975 return mask(err) 976 } 977 if len(containers) > 0 { 978 return machineNotCleanErr 979 } 980 return errgo.Newf("cannot add container within machine: transaction aborted for unknown reason") 981 } 982 983 // constraints is a helper function to return a unit's deployment constraints. 984 func (u *Unit) constraints() (*constraints.Value, error) { 985 cons, err := readConstraints(u.st, u.globalKey()) 986 if errors.IsNotFoundError(err) { 987 // Lack of constraints indicates lack of unit. 988 return nil, errors.NotFoundf("unit") 989 } else if err != nil { 990 return nil, mask(err) 991 } 992 return &cons, nil 993 } 994 995 // AssignToNewMachineOrContainer assigns the unit to a new machine, with constraints 996 // determined according to the service and environment constraints at the time of unit creation. 997 // If a container is required, a clean, empty machine instance is required on which to create 998 // the container. An existing clean, empty instance is first searched for, and if not found, 999 // a new one is created. 1000 func (u *Unit) AssignToNewMachineOrContainer() (err error) { 1001 defer assignContextf(&err, u, "new machine or container") 1002 if u.doc.Principal != "" { 1003 return errgo.Newf("unit is a subordinate") 1004 } 1005 cons, err := u.constraints() 1006 if err != nil { 1007 return mask(err) 1008 } 1009 if !cons.HasContainer() { 1010 return u.AssignToNewMachine() 1011 } 1012 1013 // Find a clean, empty machine on which to create a container. 1014 var host machineDoc 1015 hostCons := *cons 1016 noContainer := instance.NONE 1017 hostCons.Container = &noContainer 1018 query, err := u.findCleanMachineQuery(true, &hostCons) 1019 if err != nil { 1020 return mask(err) 1021 } 1022 err = query.One(&host) 1023 if errgo.Cause(err) == mgo.ErrNotFound { 1024 // No existing clean, empty machine so create a new one. 1025 // The container constraint will be used by AssignToNewMachine to create the required container. 1026 return u.AssignToNewMachine() 1027 } else if err != nil { 1028 return mask(err) 1029 } 1030 template := MachineTemplate{ 1031 Series: u.doc.Series, 1032 Constraints: *cons, 1033 Jobs: []MachineJob{JobHostUnits}, 1034 } 1035 err = u.assignToNewMachine(template, host.Id, *cons.Container) 1036 if errgo.Cause(err) == machineNotCleanErr { 1037 // The clean machine was used before we got a chance to use it so just 1038 // stick the unit on a new machine. 1039 return u.AssignToNewMachine() 1040 } 1041 return err 1042 } 1043 1044 // AssignToNewMachine assigns the unit to a new machine, with constraints 1045 // determined according to the service and environment constraints at the 1046 // time of unit creation. 1047 func (u *Unit) AssignToNewMachine() (err error) { 1048 defer assignContextf(&err, u, "new machine") 1049 if u.doc.Principal != "" { 1050 return errgo.Newf("unit is a subordinate") 1051 } 1052 // Get the ops necessary to create a new machine, and the machine doc that 1053 // will be added with those operations (which includes the machine id). 1054 cons, err := u.constraints() 1055 if err != nil { 1056 return mask(err) 1057 } 1058 var containerType instance.ContainerType 1059 // Configure to create a new container if required. 1060 if cons.HasContainer() { 1061 containerType = *cons.Container 1062 } 1063 template := MachineTemplate{ 1064 Series: u.doc.Series, 1065 Constraints: *cons, 1066 Jobs: []MachineJob{JobHostUnits}, 1067 } 1068 return u.assignToNewMachine(template, "", containerType) 1069 } 1070 1071 var noCleanMachines = errgo.New("all eligible machines in use") 1072 1073 // AssignToCleanMachine assigns u to a machine which is marked as clean. A machine 1074 // is clean if it has never had any principal units assigned to it. 1075 // If there are no clean machines besides any machine(s) running JobHostEnviron, 1076 // an error is returned. 1077 // This method does not take constraints into consideration when choosing a 1078 // machine (lp:1161919). 1079 func (u *Unit) AssignToCleanMachine() (m *Machine, err error) { 1080 return u.assignToCleanMaybeEmptyMachine(false) 1081 } 1082 1083 // AssignToCleanMachine assigns u to a machine which is marked as clean and is also 1084 // not hosting any containers. A machine is clean if it has never had any principal units 1085 // assigned to it. If there are no clean machines besides any machine(s) running JobHostEnviron, 1086 // an error is returned. 1087 // This method does not take constraints into consideration when choosing a 1088 // machine (lp:1161919). 1089 func (u *Unit) AssignToCleanEmptyMachine() (m *Machine, err error) { 1090 return u.assignToCleanMaybeEmptyMachine(true) 1091 } 1092 1093 var hasContainerTerm = bson.DocElem{ 1094 "$and", []D{ 1095 {{"children", D{{"$not", D{{"$size", 0}}}}}}, 1096 {{"children", D{{"$exists", true}}}}, 1097 }} 1098 1099 var hasNoContainersTerm = bson.DocElem{ 1100 "$or", []D{ 1101 {{"children", D{{"$size", 0}}}}, 1102 {{"children", D{{"$exists", false}}}}, 1103 }} 1104 1105 // findCleanMachineQuery returns a Mongo query to find clean (and possibly empty) machines with 1106 // characteristics matching the specified constraints. 1107 func (u *Unit) findCleanMachineQuery(requireEmpty bool, cons *constraints.Value) (*mgo.Query, error) { 1108 // Select all machines that can accept principal units and are clean. 1109 var containerRefs []machineContainers 1110 // If we need empty machines, first build up a list of machine ids which have containers 1111 // so we can exclude those. 1112 if requireEmpty { 1113 err := u.st.containerRefs.Find(D{hasContainerTerm}).All(&containerRefs) 1114 if err != nil { 1115 return nil, mask(err) 1116 } 1117 } 1118 var machinesWithContainers = make([]string, len(containerRefs)) 1119 for i, cref := range containerRefs { 1120 machinesWithContainers[i] = cref.Id 1121 } 1122 terms := D{ 1123 {"life", Alive}, 1124 {"series", u.doc.Series}, 1125 {"jobs", []MachineJob{JobHostUnits}}, 1126 {"clean", true}, 1127 {"_id", D{{"$nin", machinesWithContainers}}}, 1128 } 1129 // Add the container filter term if necessary. 1130 var containerType instance.ContainerType 1131 if cons.Container != nil { 1132 containerType = *cons.Container 1133 } 1134 if containerType == instance.NONE { 1135 terms = append(terms, bson.DocElem{"containertype", ""}) 1136 } else if containerType != "" { 1137 terms = append(terms, bson.DocElem{"containertype", string(containerType)}) 1138 } 1139 1140 // Find the ids of machines which satisfy any required hardware 1141 // constraints. If there is no instanceData for a machine, that 1142 // machine is not considered as suitable for deploying the unit. 1143 // This can happen if the machine is not yet provisioned. It may 1144 // be that when the machine is provisioned it will be found to 1145 // be suitable, but we don't know that right now and it's best 1146 // to err on the side of caution and exclude such machines. 1147 var suitableInstanceData []instanceData 1148 var suitableTerms D 1149 if cons.Arch != nil && *cons.Arch != "" { 1150 suitableTerms = append(suitableTerms, bson.DocElem{"arch", *cons.Arch}) 1151 } 1152 if cons.Mem != nil && *cons.Mem > 0 { 1153 suitableTerms = append(suitableTerms, bson.DocElem{"mem", D{{"$gte", *cons.Mem}}}) 1154 } 1155 if cons.RootDisk != nil && *cons.RootDisk > 0 { 1156 suitableTerms = append(suitableTerms, bson.DocElem{"rootdisk", D{{"$gte", *cons.RootDisk}}}) 1157 } 1158 if cons.CpuCores != nil && *cons.CpuCores > 0 { 1159 suitableTerms = append(suitableTerms, bson.DocElem{"cpucores", D{{"$gte", *cons.CpuCores}}}) 1160 } 1161 if cons.CpuPower != nil && *cons.CpuPower > 0 { 1162 suitableTerms = append(suitableTerms, bson.DocElem{"cpupower", D{{"$gte", *cons.CpuPower}}}) 1163 } 1164 if cons.Tags != nil && len(*cons.Tags) > 0 { 1165 suitableTerms = append(suitableTerms, bson.DocElem{"tags", D{{"$all", *cons.Tags}}}) 1166 } 1167 if len(suitableTerms) > 0 { 1168 err := u.st.instanceData.Find(suitableTerms).Select(bson.M{"_id": 1}).All(&suitableInstanceData) 1169 if err != nil { 1170 return nil, mask(err) 1171 } 1172 var suitableIds = make([]string, len(suitableInstanceData)) 1173 for i, m := range suitableInstanceData { 1174 suitableIds[i] = m.Id 1175 } 1176 terms = append(terms, bson.DocElem{"_id", D{{"$in", suitableIds}}}) 1177 } 1178 return u.st.machines.Find(terms), nil 1179 } 1180 1181 // assignToCleanMaybeEmptyMachine implements AssignToCleanMachine and AssignToCleanEmptyMachine. 1182 // A 'machine' may be a machine instance or container depending on the service constraints. 1183 func (u *Unit) assignToCleanMaybeEmptyMachine(requireEmpty bool) (m *Machine, err error) { 1184 context := "clean" 1185 if requireEmpty { 1186 context += ", empty" 1187 } 1188 context += " machine" 1189 1190 if u.doc.Principal != "" { 1191 err = errgo.Newf("unit is a subordinate") 1192 assignContextf(&err, u, context) 1193 return nil, err 1194 } 1195 1196 // Get the unit constraints to see what deployment requirements we have to adhere to. 1197 cons, err := u.constraints() 1198 if err != nil { 1199 assignContextf(&err, u, context) 1200 return nil, err 1201 } 1202 query, err := u.findCleanMachineQuery(requireEmpty, cons) 1203 if err != nil { 1204 assignContextf(&err, u, context) 1205 return nil, err 1206 } 1207 1208 // TODO(rog) Fix so this is more efficient when there are concurrent uses. 1209 // Possible solution: pick the highest and the smallest id of all 1210 // unused machines, and try to assign to the first one >= a random id in the 1211 // middle. 1212 iter := query.Batch(1).Prefetch(0).Iter() 1213 var mdoc machineDoc 1214 for iter.Next(&mdoc) { 1215 m := newMachine(u.st, &mdoc) 1216 err := u.assignToMachine(m, true) 1217 if err == nil { 1218 return m, nil 1219 } 1220 if err != inUseErr && err != machineNotAliveErr { 1221 assignContextf(&err, u, context) 1222 return nil, err 1223 } 1224 } 1225 if err := iter.Err(); err != nil { 1226 assignContextf(&err, u, context) 1227 return nil, err 1228 } 1229 return nil, noCleanMachines 1230 } 1231 1232 // UnassignFromMachine removes the assignment between this unit and the 1233 // machine it's assigned to. 1234 func (u *Unit) UnassignFromMachine() (err error) { 1235 // TODO check local machine id and add an assert that the 1236 // machine id is as expected. 1237 ops := []txn.Op{{ 1238 C: u.st.units.Name, 1239 Id: u.doc.Name, 1240 Assert: txn.DocExists, 1241 Update: D{{"$set", D{{"machineid", ""}}}}, 1242 }} 1243 if u.doc.MachineId != "" { 1244 ops = append(ops, txn.Op{ 1245 C: u.st.machines.Name, 1246 Id: u.doc.MachineId, 1247 Assert: txn.DocExists, 1248 Update: D{{"$pull", D{{"principals", u.doc.Name}}}}, 1249 }) 1250 } 1251 err = u.st.runTransaction(ops) 1252 if err != nil { 1253 return errgo.Newf("cannot unassign unit %q from machine: %v", u, onAbort(err, errors.NotFoundf("machine"))) 1254 } 1255 u.doc.MachineId = "" 1256 return nil 1257 } 1258 1259 // SetPublicAddress sets the public address of the unit. 1260 func (u *Unit) SetPublicAddress(address string) (err error) { 1261 ops := []txn.Op{{ 1262 C: u.st.units.Name, 1263 Id: u.doc.Name, 1264 Assert: txn.DocExists, 1265 Update: D{{"$set", D{{"publicaddress", address}}}}, 1266 }} 1267 if err := u.st.runTransaction(ops); err != nil { 1268 return errgo.Newf("cannot set public address of unit %q: %v", u, onAbort(err, errors.NotFoundf("unit"))) 1269 } 1270 u.doc.PublicAddress = address 1271 return nil 1272 } 1273 1274 // SetPrivateAddress sets the private address of the unit. 1275 func (u *Unit) SetPrivateAddress(address string) error { 1276 ops := []txn.Op{{ 1277 C: u.st.units.Name, 1278 Id: u.doc.Name, 1279 Assert: notDeadDoc, 1280 Update: D{{"$set", D{{"privateaddress", address}}}}, 1281 }} 1282 err := u.st.runTransaction(ops) 1283 if err != nil { 1284 return errgo.Newf("cannot set private address of unit %q: %v", u, onAbort(err, errors.NotFoundf("unit"))) 1285 } 1286 u.doc.PrivateAddress = address 1287 return nil 1288 } 1289 1290 // Resolve marks the unit as having had any previous state transition 1291 // problems resolved, and informs the unit that it may attempt to 1292 // reestablish normal workflow. The retryHooks parameter informs 1293 // whether to attempt to reexecute previous failed hooks or to continue 1294 // as if they had succeeded before. 1295 func (u *Unit) Resolve(retryHooks bool) error { 1296 status, _, _, err := u.Status() 1297 if err != nil { 1298 return mask(err) 1299 } 1300 if status != params.StatusError { 1301 return errgo.Newf("unit %q is not in an error state", u) 1302 } 1303 mode := ResolvedNoHooks 1304 if retryHooks { 1305 mode = ResolvedRetryHooks 1306 } 1307 return u.SetResolved(mode) 1308 } 1309 1310 // SetResolved marks the unit as having had any previous state transition 1311 // problems resolved, and informs the unit that it may attempt to 1312 // reestablish normal workflow. The resolved mode parameter informs 1313 // whether to attempt to reexecute previous failed hooks or to continue 1314 // as if they had succeeded before. 1315 func (u *Unit) SetResolved(mode ResolvedMode) (err error) { 1316 defer utils.ErrorContextf(&err, "cannot set resolved mode for unit %q", u) 1317 switch mode { 1318 case ResolvedRetryHooks, ResolvedNoHooks: 1319 default: 1320 return errgo.Newf("invalid error resolution mode: %q", mode) 1321 } 1322 // TODO(fwereade): assert unit has error status. 1323 resolvedNotSet := D{{"resolved", ResolvedNone}} 1324 ops := []txn.Op{{ 1325 C: u.st.units.Name, 1326 Id: u.doc.Name, 1327 Assert: append(notDeadDoc, resolvedNotSet...), 1328 Update: D{{"$set", D{{"resolved", mode}}}}, 1329 }} 1330 if err := u.st.runTransaction(ops); err == nil { 1331 u.doc.Resolved = mode 1332 return nil 1333 } else if err != txn.ErrAborted { 1334 return mask(err) 1335 } 1336 if ok, err := isNotDead(u.st.units, u.doc.Name); err != nil { 1337 return mask(err) 1338 } else if !ok { 1339 return errDead 1340 } 1341 // For now, the only remaining assert is that resolved was unset. 1342 return errgo.Newf("already resolved") 1343 } 1344 1345 // ClearResolved removes any resolved setting on the unit. 1346 func (u *Unit) ClearResolved() error { 1347 ops := []txn.Op{{ 1348 C: u.st.units.Name, 1349 Id: u.doc.Name, 1350 Assert: txn.DocExists, 1351 Update: D{{"$set", D{{"resolved", ResolvedNone}}}}, 1352 }} 1353 err := u.st.runTransaction(ops) 1354 if err != nil { 1355 return errgo.Newf("cannot clear resolved mode for unit %q: %v", u, errors.NotFoundf("unit")) 1356 } 1357 u.doc.Resolved = ResolvedNone 1358 return nil 1359 }