github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/service.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 stderrors "errors" 8 "fmt" 9 "sort" 10 "strconv" 11 "strings" 12 13 "github.com/juju/errors" 14 "github.com/juju/names" 15 "labix.org/v2/mgo" 16 "labix.org/v2/mgo/bson" 17 "labix.org/v2/mgo/txn" 18 19 "github.com/juju/juju/charm" 20 "github.com/juju/juju/constraints" 21 "github.com/juju/juju/state/api/params" 22 ) 23 24 // Service represents the state of a service. 25 type Service struct { 26 st *State 27 doc serviceDoc 28 annotator 29 } 30 31 // serviceDoc represents the internal state of a service in MongoDB. 32 // Note the correspondence with ServiceInfo in state/api/params. 33 type serviceDoc struct { 34 Name string `bson:"_id"` 35 Series string 36 Subordinate bool 37 CharmURL *charm.URL 38 ForceCharm bool 39 Life Life 40 UnitSeq int 41 UnitCount int 42 RelationCount int 43 Exposed bool 44 MinUnits int 45 OwnerTag string 46 TxnRevno int64 `bson:"txn-revno"` 47 } 48 49 func newService(st *State, doc *serviceDoc) *Service { 50 svc := &Service{ 51 st: st, 52 doc: *doc, 53 } 54 svc.annotator = annotator{ 55 globalKey: svc.globalKey(), 56 tag: svc.Tag(), 57 st: st, 58 } 59 return svc 60 } 61 62 // Name returns the service name. 63 func (s *Service) Name() string { 64 return s.doc.Name 65 } 66 67 // Tag returns a name identifying the service that is safe to use 68 // as a file name. The returned name will be different from other 69 // Tag values returned by any other entities from the same state. 70 func (s *Service) Tag() string { 71 return names.ServiceTag(s.Name()) 72 } 73 74 // serviceGlobalKey returns the global database key for the service 75 // with the given name. 76 func serviceGlobalKey(svcName string) string { 77 return "s#" + svcName 78 } 79 80 // globalKey returns the global database key for the service. 81 func (s *Service) globalKey() string { 82 return serviceGlobalKey(s.doc.Name) 83 } 84 85 func serviceSettingsKey(serviceName string, curl *charm.URL) string { 86 return fmt.Sprintf("s#%s#%s", serviceName, curl) 87 } 88 89 // settingsKey returns the charm-version-specific settings collection 90 // key for the service. 91 func (s *Service) settingsKey() string { 92 return serviceSettingsKey(s.doc.Name, s.doc.CharmURL) 93 } 94 95 // Life returns whether the service is Alive, Dying or Dead. 96 func (s *Service) Life() Life { 97 return s.doc.Life 98 } 99 100 var errRefresh = stderrors.New("state seems inconsistent, refresh and try again") 101 102 // Destroy ensures that the service and all its relations will be removed at 103 // some point; if the service has no units, and no relation involving the 104 // service has any units in scope, they are all removed immediately. 105 func (s *Service) Destroy() (err error) { 106 defer errors.Maskf(&err, "cannot destroy service %q", s) 107 defer func() { 108 if err == nil { 109 // This is a white lie; the document might actually be removed. 110 s.doc.Life = Dying 111 } 112 }() 113 svc := &Service{st: s.st, doc: s.doc} 114 for i := 0; i < 5; i++ { 115 switch ops, err := svc.destroyOps(); err { 116 case errRefresh: 117 case errAlreadyDying: 118 return nil 119 case nil: 120 if err := svc.st.runTransaction(ops); err != txn.ErrAborted { 121 return err 122 } 123 default: 124 return err 125 } 126 if err := svc.Refresh(); errors.IsNotFound(err) { 127 return nil 128 } else if err != nil { 129 return err 130 } 131 } 132 return ErrExcessiveContention 133 } 134 135 // destroyOps returns the operations required to destroy the service. If it 136 // returns errRefresh, the service should be refreshed and the destruction 137 // operations recalculated. 138 func (s *Service) destroyOps() ([]txn.Op, error) { 139 if s.doc.Life == Dying { 140 return nil, errAlreadyDying 141 } 142 rels, err := s.Relations() 143 if err != nil { 144 return nil, err 145 } 146 if len(rels) != s.doc.RelationCount { 147 // This is just an early bail out. The relations obtained may still 148 // be wrong, but that situation will be caught by a combination of 149 // asserts on relationcount and on each known relation, below. 150 return nil, errRefresh 151 } 152 ops := []txn.Op{minUnitsRemoveOp(s.st, s.doc.Name)} 153 removeCount := 0 154 for _, rel := range rels { 155 relOps, isRemove, err := rel.destroyOps(s.doc.Name) 156 if err == errAlreadyDying { 157 relOps = []txn.Op{{ 158 C: s.st.relations.Name, 159 Id: rel.doc.Key, 160 Assert: bson.D{{"life", Dying}}, 161 }} 162 } else if err != nil { 163 return nil, err 164 } 165 if isRemove { 166 removeCount++ 167 } 168 ops = append(ops, relOps...) 169 } 170 // If the service has no units, and all its known relations will be 171 // removed, the service can also be removed. 172 if s.doc.UnitCount == 0 && s.doc.RelationCount == removeCount { 173 hasLastRefs := bson.D{{"life", Alive}, {"unitcount", 0}, {"relationcount", removeCount}} 174 return append(ops, s.removeOps(hasLastRefs)...), nil 175 } 176 // In all other cases, service removal will be handled as a consequence 177 // of the removal of the last unit or relation referencing it. If any 178 // relations have been removed, they'll be caught by the operations 179 // collected above; but if any has been added, we need to abort and add 180 // a destroy op for that relation too. In combination, it's enough to 181 // check for count equality: an add/remove will not touch the count, but 182 // will be caught by virtue of being a remove. 183 notLastRefs := bson.D{ 184 {"life", Alive}, 185 {"relationcount", s.doc.RelationCount}, 186 } 187 // With respect to unit count, a changing value doesn't matter, so long 188 // as the count's equality with zero does not change, because all we care 189 // about is that *some* unit is, or is not, keeping the service from 190 // being removed: the difference between 1 unit and 1000 is irrelevant. 191 if s.doc.UnitCount > 0 { 192 ops = append(ops, s.st.newCleanupOp(cleanupUnitsForDyingService, s.doc.Name+"/")) 193 notLastRefs = append(notLastRefs, bson.D{{"unitcount", bson.D{{"$gt", 0}}}}...) 194 } else { 195 notLastRefs = append(notLastRefs, bson.D{{"unitcount", 0}}...) 196 } 197 update := bson.D{{"$set", bson.D{{"life", Dying}}}} 198 if removeCount != 0 { 199 decref := bson.D{{"$inc", bson.D{{"relationcount", -removeCount}}}} 200 update = append(update, decref...) 201 } 202 return append(ops, txn.Op{ 203 C: s.st.services.Name, 204 Id: s.doc.Name, 205 Assert: notLastRefs, 206 Update: update, 207 }), nil 208 } 209 210 // removeOps returns the operations required to remove the service. Supplied 211 // asserts will be included in the operation on the service document. 212 func (s *Service) removeOps(asserts bson.D) []txn.Op { 213 ops := []txn.Op{{ 214 C: s.st.services.Name, 215 Id: s.doc.Name, 216 Assert: asserts, 217 Remove: true, 218 }, { 219 C: s.st.settingsrefs.Name, 220 Id: s.settingsKey(), 221 Remove: true, 222 }, { 223 C: s.st.settings.Name, 224 Id: s.settingsKey(), 225 Remove: true, 226 }} 227 ops = append(ops, removeRequestedNetworksOp(s.st, s.globalKey())) 228 ops = append(ops, removeConstraintsOp(s.st, s.globalKey())) 229 return append(ops, annotationRemoveOp(s.st, s.globalKey())) 230 } 231 232 // IsExposed returns whether this service is exposed. The explicitly open 233 // ports (with open-port) for exposed services may be accessed from machines 234 // outside of the local deployment network. See SetExposed and ClearExposed. 235 func (s *Service) IsExposed() bool { 236 return s.doc.Exposed 237 } 238 239 // SetExposed marks the service as exposed. 240 // See ClearExposed and IsExposed. 241 func (s *Service) SetExposed() error { 242 return s.setExposed(true) 243 } 244 245 // ClearExposed removes the exposed flag from the service. 246 // See SetExposed and IsExposed. 247 func (s *Service) ClearExposed() error { 248 return s.setExposed(false) 249 } 250 251 func (s *Service) setExposed(exposed bool) (err error) { 252 ops := []txn.Op{{ 253 C: s.st.services.Name, 254 Id: s.doc.Name, 255 Assert: isAliveDoc, 256 Update: bson.D{{"$set", bson.D{{"exposed", exposed}}}}, 257 }} 258 if err := s.st.runTransaction(ops); err != nil { 259 return fmt.Errorf("cannot set exposed flag for service %q to %v: %v", s, exposed, onAbort(err, errNotAlive)) 260 } 261 s.doc.Exposed = exposed 262 return nil 263 } 264 265 // Charm returns the service's charm and whether units should upgrade to that 266 // charm even if they are in an error state. 267 func (s *Service) Charm() (ch *Charm, force bool, err error) { 268 ch, err = s.st.Charm(s.doc.CharmURL) 269 if err != nil { 270 return nil, false, err 271 } 272 return ch, s.doc.ForceCharm, nil 273 } 274 275 // IsPrincipal returns whether units of the service can 276 // have subordinate units. 277 func (s *Service) IsPrincipal() bool { 278 return !s.doc.Subordinate 279 } 280 281 // CharmURL returns the service's charm URL, and whether units should upgrade 282 // to the charm with that URL even if they are in an error state. 283 func (s *Service) CharmURL() (curl *charm.URL, force bool) { 284 return s.doc.CharmURL, s.doc.ForceCharm 285 } 286 287 // Endpoints returns the service's currently available relation endpoints. 288 func (s *Service) Endpoints() (eps []Endpoint, err error) { 289 ch, _, err := s.Charm() 290 if err != nil { 291 return nil, err 292 } 293 collect := func(role charm.RelationRole, rels map[string]charm.Relation) { 294 for _, rel := range rels { 295 eps = append(eps, Endpoint{ 296 ServiceName: s.doc.Name, 297 Relation: rel, 298 }) 299 } 300 } 301 meta := ch.Meta() 302 collect(charm.RolePeer, meta.Peers) 303 collect(charm.RoleProvider, meta.Provides) 304 collect(charm.RoleRequirer, meta.Requires) 305 collect(charm.RoleProvider, map[string]charm.Relation{ 306 "juju-info": { 307 Name: "juju-info", 308 Role: charm.RoleProvider, 309 Interface: "juju-info", 310 Scope: charm.ScopeGlobal, 311 }, 312 }) 313 sort.Sort(epSlice(eps)) 314 return eps, nil 315 } 316 317 // Endpoint returns the relation endpoint with the supplied name, if it exists. 318 func (s *Service) Endpoint(relationName string) (Endpoint, error) { 319 eps, err := s.Endpoints() 320 if err != nil { 321 return Endpoint{}, err 322 } 323 for _, ep := range eps { 324 if ep.Name == relationName { 325 return ep, nil 326 } 327 } 328 return Endpoint{}, fmt.Errorf("service %q has no %q relation", s, relationName) 329 } 330 331 // extraPeerRelations returns only the peer relations in newMeta not 332 // present in the service's current charm meta data. 333 func (s *Service) extraPeerRelations(newMeta *charm.Meta) map[string]charm.Relation { 334 if newMeta == nil { 335 // This should never happen, since we're checking the charm in SetCharm already. 336 panic("newMeta is nil") 337 } 338 ch, _, err := s.Charm() 339 if err != nil { 340 return nil 341 } 342 newPeers := newMeta.Peers 343 oldPeers := ch.Meta().Peers 344 extraPeers := make(map[string]charm.Relation) 345 for relName, rel := range newPeers { 346 if _, ok := oldPeers[relName]; !ok { 347 extraPeers[relName] = rel 348 } 349 } 350 return extraPeers 351 } 352 353 func (s *Service) checkRelationsOps(ch *Charm, relations []*Relation) ([]txn.Op, error) { 354 asserts := make([]txn.Op, 0, len(relations)) 355 // All relations must still exist and their endpoints are implemented by the charm. 356 for _, rel := range relations { 357 if ep, err := rel.Endpoint(s.doc.Name); err != nil { 358 return nil, err 359 } else if !ep.ImplementedBy(ch) { 360 return nil, fmt.Errorf("cannot upgrade service %q to charm %q: would break relation %q", s, ch, rel) 361 } 362 asserts = append(asserts, txn.Op{ 363 C: s.st.relations.Name, 364 Id: rel.doc.Key, 365 Assert: txn.DocExists, 366 }) 367 } 368 return asserts, nil 369 } 370 371 // changeCharmOps returns the operations necessary to set a service's 372 // charm URL to a new value. 373 func (s *Service) changeCharmOps(ch *Charm, force bool) ([]txn.Op, error) { 374 // Build the new service config from what can be used of the old one. 375 oldSettings, err := readSettings(s.st, s.settingsKey()) 376 if err != nil { 377 return nil, err 378 } 379 newSettings := ch.Config().FilterSettings(oldSettings.Map()) 380 381 // Create or replace service settings. 382 var settingsOp txn.Op 383 newKey := serviceSettingsKey(s.doc.Name, ch.URL()) 384 if count, err := s.st.settings.FindId(newKey).Count(); err != nil { 385 return nil, err 386 } else if count == 0 { 387 // No settings for this key yet, create it. 388 settingsOp = createSettingsOp(s.st, newKey, newSettings) 389 } else { 390 // Settings exist, just replace them with the new ones. 391 var err error 392 settingsOp, _, err = replaceSettingsOp(s.st, newKey, newSettings) 393 if err != nil { 394 return nil, err 395 } 396 } 397 398 // Add or create a reference to the new settings doc. 399 incOp, err := settingsIncRefOp(s.st, s.doc.Name, ch.URL(), true) 400 if err != nil { 401 return nil, err 402 } 403 // Drop the reference to the old settings doc. 404 decOps, err := settingsDecRefOps(s.st, s.doc.Name, s.doc.CharmURL) // current charm 405 if err != nil { 406 return nil, err 407 } 408 409 // Build the transaction. 410 differentCharm := bson.D{{"charmurl", bson.D{{"$ne", ch.URL()}}}} 411 ops := []txn.Op{ 412 // Old settings shouldn't change 413 oldSettings.assertUnchangedOp(), 414 // Create/replace with new settings. 415 settingsOp, 416 // Increment the ref count. 417 incOp, 418 // Update the charm URL and force flag (if relevant). 419 { 420 C: s.st.services.Name, 421 Id: s.doc.Name, 422 Assert: append(isAliveDoc, differentCharm...), 423 Update: bson.D{{"$set", bson.D{{"charmurl", ch.URL()}, {"forcecharm", force}}}}, 424 }, 425 } 426 // Add any extra peer relations that need creation. 427 newPeers := s.extraPeerRelations(ch.Meta()) 428 peerOps, err := s.st.addPeerRelationsOps(s.doc.Name, newPeers) 429 if err != nil { 430 return nil, err 431 } 432 433 // Get all relations - we need to check them later. 434 relations, err := s.Relations() 435 if err != nil { 436 return nil, err 437 } 438 // Make sure the relation count does not change. 439 sameRelCount := bson.D{{"relationcount", len(relations)}} 440 441 ops = append(ops, peerOps...) 442 // Update the relation count as well. 443 ops = append(ops, txn.Op{ 444 C: s.st.services.Name, 445 Id: s.doc.Name, 446 Assert: append(isAliveDoc, sameRelCount...), 447 Update: bson.D{{"$inc", bson.D{{"relationcount", len(newPeers)}}}}, 448 }) 449 // Check relations to ensure no active relations are removed. 450 relOps, err := s.checkRelationsOps(ch, relations) 451 if err != nil { 452 return nil, err 453 } 454 ops = append(ops, relOps...) 455 456 // And finally, decrement the old settings. 457 return append(ops, decOps...), nil 458 } 459 460 // SetCharm changes the charm for the service. New units will be started with 461 // this charm, and existing units will be upgraded to use it. If force is true, 462 // units will be upgraded even if they are in an error state. 463 func (s *Service) SetCharm(ch *Charm, force bool) (err error) { 464 if ch.Meta().Subordinate != s.doc.Subordinate { 465 return fmt.Errorf("cannot change a service's subordinacy") 466 } 467 if ch.URL().Series != s.doc.Series { 468 return fmt.Errorf("cannot change a service's series") 469 } 470 for i := 0; i < 5; i++ { 471 var ops []txn.Op 472 // Make sure the service doesn't have this charm already. 473 sel := bson.D{{"_id", s.doc.Name}, {"charmurl", ch.URL()}} 474 if count, err := s.st.services.Find(sel).Count(); err != nil { 475 return err 476 } else if count == 1 { 477 // Charm URL already set; just update the force flag. 478 sameCharm := bson.D{{"charmurl", ch.URL()}} 479 ops = []txn.Op{{ 480 C: s.st.services.Name, 481 Id: s.doc.Name, 482 Assert: append(isAliveDoc, sameCharm...), 483 Update: bson.D{{"$set", bson.D{{"forcecharm", force}}}}, 484 }} 485 } else { 486 // Change the charm URL. 487 ops, err = s.changeCharmOps(ch, force) 488 if err != nil { 489 return err 490 } 491 } 492 493 if err := s.st.runTransaction(ops); err == nil { 494 s.doc.CharmURL = ch.URL() 495 s.doc.ForceCharm = force 496 return nil 497 } else if err != txn.ErrAborted { 498 return err 499 } 500 501 // If the service is not alive, fail out immediately; otherwise, 502 // data changed underneath us, so retry. 503 if alive, err := isAlive(s.st.services, s.doc.Name); err != nil { 504 return err 505 } else if !alive { 506 return fmt.Errorf("service %q is not alive", s.doc.Name) 507 } 508 } 509 return ErrExcessiveContention 510 } 511 512 // String returns the service name. 513 func (s *Service) String() string { 514 return s.doc.Name 515 } 516 517 // Refresh refreshes the contents of the Service from the underlying 518 // state. It returns an error that satisfies errors.IsNotFound if the 519 // service has been removed. 520 func (s *Service) Refresh() error { 521 err := s.st.services.FindId(s.doc.Name).One(&s.doc) 522 if err == mgo.ErrNotFound { 523 return errors.NotFoundf("service %q", s) 524 } 525 if err != nil { 526 return fmt.Errorf("cannot refresh service %q: %v", s, err) 527 } 528 return nil 529 } 530 531 // newUnitName returns the next unit name. 532 func (s *Service) newUnitName() (string, error) { 533 change := mgo.Change{Update: bson.D{{"$inc", bson.D{{"unitseq", 1}}}}} 534 result := serviceDoc{} 535 if _, err := s.st.services.Find(bson.D{{"_id", s.doc.Name}}).Apply(change, &result); err == mgo.ErrNotFound { 536 return "", errors.NotFoundf("service %q", s) 537 } else if err != nil { 538 return "", fmt.Errorf("cannot increment unit sequence: %v", err) 539 } 540 name := s.doc.Name + "/" + strconv.Itoa(result.UnitSeq) 541 return name, nil 542 } 543 544 // addUnitOps returns a unique name for a new unit, and a list of txn operations 545 // necessary to create that unit. The principalName param must be non-empty if 546 // and only if s is a subordinate service. Only one subordinate of a given 547 // service will be assigned to a given principal. The asserts param can be used 548 // to include additional assertions for the service document. 549 func (s *Service) addUnitOps(principalName string, asserts bson.D) (string, []txn.Op, error) { 550 if s.doc.Subordinate && principalName == "" { 551 return "", nil, fmt.Errorf("service is a subordinate") 552 } else if !s.doc.Subordinate && principalName != "" { 553 return "", nil, fmt.Errorf("service is not a subordinate") 554 } 555 name, err := s.newUnitName() 556 if err != nil { 557 return "", nil, err 558 } 559 globalKey := unitGlobalKey(name) 560 udoc := &unitDoc{ 561 Name: name, 562 Service: s.doc.Name, 563 Series: s.doc.Series, 564 Life: Alive, 565 Principal: principalName, 566 } 567 sdoc := statusDoc{ 568 Status: params.StatusPending, 569 } 570 ops := []txn.Op{ 571 { 572 C: s.st.units.Name, 573 Id: name, 574 Assert: txn.DocMissing, 575 Insert: udoc, 576 }, 577 createStatusOp(s.st, globalKey, sdoc), 578 { 579 C: s.st.services.Name, 580 Id: s.doc.Name, 581 Assert: append(isAliveDoc, asserts...), 582 Update: bson.D{{"$inc", bson.D{{"unitcount", 1}}}}, 583 }} 584 if s.doc.Subordinate { 585 ops = append(ops, txn.Op{ 586 C: s.st.units.Name, 587 Id: principalName, 588 Assert: append(isAliveDoc, bson.DocElem{ 589 "subordinates", bson.D{{"$not", bson.RegEx{Pattern: "^" + s.doc.Name + "/"}}}, 590 }), 591 Update: bson.D{{"$addToSet", bson.D{{"subordinates", name}}}}, 592 }) 593 } else { 594 scons, err := s.Constraints() 595 if err != nil { 596 return "", nil, err 597 } 598 cons, err := s.st.resolveConstraints(scons) 599 if err != nil { 600 return "", nil, err 601 } 602 ops = append(ops, createConstraintsOp(s.st, globalKey, cons)) 603 } 604 return name, ops, nil 605 } 606 607 // GetOwnerTag returns the owner of this service 608 // SCHEMACHANGE 609 // TODO(mattyw) remove when schema upgrades are possible 610 func (s *serviceDoc) GetOwnerTag() string { 611 if s.OwnerTag != "" { 612 return s.OwnerTag 613 } 614 return "user-admin" 615 } 616 617 // SCHEMACHANGE 618 // TODO(mattyw) remove when schema upgrades are possible 619 func (s *Service) GetOwnerTag() string { 620 return s.doc.GetOwnerTag() 621 } 622 623 // AddUnit adds a new principal unit to the service. 624 func (s *Service) AddUnit() (unit *Unit, err error) { 625 defer errors.Maskf(&err, "cannot add unit to service %q", s) 626 name, ops, err := s.addUnitOps("", nil) 627 if err != nil { 628 return nil, err 629 } 630 if err := s.st.runTransaction(ops); err == txn.ErrAborted { 631 if alive, err := isAlive(s.st.services, s.doc.Name); err != nil { 632 return nil, err 633 } else if !alive { 634 return nil, fmt.Errorf("service is not alive") 635 } 636 return nil, fmt.Errorf("inconsistent state") 637 } else if err != nil { 638 return nil, err 639 } 640 return s.Unit(name) 641 } 642 643 var ErrExcessiveContention = stderrors.New("state changing too quickly; try again soon") 644 645 // removeUnitOps returns the operations necessary to remove the supplied unit, 646 // assuming the supplied asserts apply to the unit document. 647 func (s *Service) removeUnitOps(u *Unit, asserts bson.D) ([]txn.Op, error) { 648 var ops []txn.Op 649 if s.doc.Subordinate { 650 ops = append(ops, txn.Op{ 651 C: s.st.units.Name, 652 Id: u.doc.Principal, 653 Assert: txn.DocExists, 654 Update: bson.D{{"$pull", bson.D{{"subordinates", u.doc.Name}}}}, 655 }) 656 } else if u.doc.MachineId != "" { 657 ops = append(ops, txn.Op{ 658 C: s.st.machines.Name, 659 Id: u.doc.MachineId, 660 Assert: txn.DocExists, 661 Update: bson.D{{"$pull", bson.D{{"principals", u.doc.Name}}}}, 662 }) 663 } 664 observedFieldsMatch := bson.D{ 665 {"charmurl", u.doc.CharmURL}, 666 {"machineid", u.doc.MachineId}, 667 } 668 ops = append(ops, txn.Op{ 669 C: s.st.units.Name, 670 Id: u.doc.Name, 671 Assert: append(observedFieldsMatch, asserts...), 672 Remove: true, 673 }, 674 removeConstraintsOp(s.st, u.globalKey()), 675 removeStatusOp(s.st, u.globalKey()), 676 annotationRemoveOp(s.st, u.globalKey()), 677 s.st.newCleanupOp(cleanupRemovedUnit, u.doc.Name), 678 ) 679 if u.doc.CharmURL != nil { 680 decOps, err := settingsDecRefOps(s.st, s.doc.Name, u.doc.CharmURL) 681 if errors.IsNotFound(err) { 682 return nil, errRefresh 683 } else if err != nil { 684 return nil, err 685 } 686 ops = append(ops, decOps...) 687 } 688 if s.doc.Life == Dying && s.doc.RelationCount == 0 && s.doc.UnitCount == 1 { 689 hasLastRef := bson.D{{"life", Dying}, {"relationcount", 0}, {"unitcount", 1}} 690 return append(ops, s.removeOps(hasLastRef)...), nil 691 } 692 svcOp := txn.Op{ 693 C: s.st.services.Name, 694 Id: s.doc.Name, 695 Update: bson.D{{"$inc", bson.D{{"unitcount", -1}}}}, 696 } 697 if s.doc.Life == Alive { 698 svcOp.Assert = bson.D{{"life", Alive}, {"unitcount", bson.D{{"$gt", 0}}}} 699 } else { 700 svcOp.Assert = bson.D{ 701 {"life", Dying}, 702 {"$or", []bson.D{ 703 {{"unitcount", bson.D{{"$gt", 1}}}}, 704 {{"relationcount", bson.D{{"$gt", 0}}}}, 705 }}, 706 } 707 } 708 return append(ops, svcOp), nil 709 } 710 711 // Unit returns the service's unit with name. 712 func (s *Service) Unit(name string) (*Unit, error) { 713 if !names.IsUnit(name) { 714 return nil, fmt.Errorf("%q is not a valid unit name", name) 715 } 716 udoc := &unitDoc{} 717 sel := bson.D{{"_id", name}, {"service", s.doc.Name}} 718 if err := s.st.units.Find(sel).One(udoc); err != nil { 719 return nil, fmt.Errorf("cannot get unit %q from service %q: %v", name, s.doc.Name, err) 720 } 721 return newUnit(s.st, udoc), nil 722 } 723 724 // AllUnits returns all units of the service. 725 func (s *Service) AllUnits() (units []*Unit, err error) { 726 return allUnits(s.st, s.doc.Name) 727 } 728 729 func allUnits(st *State, service string) (units []*Unit, err error) { 730 docs := []unitDoc{} 731 err = st.units.Find(bson.D{{"service", service}}).All(&docs) 732 if err != nil { 733 return nil, fmt.Errorf("cannot get all units from service %q: %v", service, err) 734 } 735 for i := range docs { 736 units = append(units, newUnit(st, &docs[i])) 737 } 738 return units, nil 739 } 740 741 // Relations returns a Relation for every relation the service is in. 742 func (s *Service) Relations() (relations []*Relation, err error) { 743 return serviceRelations(s.st, s.doc.Name) 744 } 745 746 func serviceRelations(st *State, name string) (relations []*Relation, err error) { 747 defer errors.Maskf(&err, "can't get relations for service %q", name) 748 docs := []relationDoc{} 749 err = st.relations.Find(bson.D{{"endpoints.servicename", name}}).All(&docs) 750 if err != nil { 751 return nil, err 752 } 753 for _, v := range docs { 754 relations = append(relations, newRelation(st, &v)) 755 } 756 return relations, nil 757 } 758 759 // ConfigSettings returns the raw user configuration for the service's charm. 760 // Unset values are omitted. 761 func (s *Service) ConfigSettings() (charm.Settings, error) { 762 settings, err := readSettings(s.st, s.settingsKey()) 763 if err != nil { 764 return nil, err 765 } 766 return settings.Map(), nil 767 } 768 769 // UpdateConfigSettings changes a service's charm config settings. Values set 770 // to nil will be deleted; unknown and invalid values will return an error. 771 func (s *Service) UpdateConfigSettings(changes charm.Settings) error { 772 charm, _, err := s.Charm() 773 if err != nil { 774 return err 775 } 776 changes, err = charm.Config().ValidateSettings(changes) 777 if err != nil { 778 return err 779 } 780 // TODO(fwereade) state.Settings is itself really problematic in just 781 // about every use case. This needs to be resolved some time; but at 782 // least the settings docs are keyed by charm url as well as service 783 // name, so the actual impact of a race is non-threatening. 784 node, err := readSettings(s.st, s.settingsKey()) 785 if err != nil { 786 return err 787 } 788 for name, value := range changes { 789 if value == nil { 790 node.Delete(name) 791 } else { 792 node.Set(name, value) 793 } 794 } 795 _, err = node.Write() 796 return err 797 } 798 799 var ErrSubordinateConstraints = stderrors.New("constraints do not apply to subordinate services") 800 801 // Constraints returns the current service constraints. 802 func (s *Service) Constraints() (constraints.Value, error) { 803 if s.doc.Subordinate { 804 return constraints.Value{}, ErrSubordinateConstraints 805 } 806 return readConstraints(s.st, s.globalKey()) 807 } 808 809 // SetConstraints replaces the current service constraints. 810 func (s *Service) SetConstraints(cons constraints.Value) (err error) { 811 unsupported, err := s.st.validateConstraints(cons) 812 if len(unsupported) > 0 { 813 logger.Warningf( 814 "setting constraints on service %q: unsupported constraints: %v", s.Name(), strings.Join(unsupported, ",")) 815 } else if err != nil { 816 return err 817 } 818 if s.doc.Subordinate { 819 return ErrSubordinateConstraints 820 } 821 defer errors.Maskf(&err, "cannot set constraints") 822 if s.doc.Life != Alive { 823 return errNotAlive 824 } 825 ops := []txn.Op{ 826 { 827 C: s.st.services.Name, 828 Id: s.doc.Name, 829 Assert: isAliveDoc, 830 }, 831 setConstraintsOp(s.st, s.globalKey(), cons), 832 } 833 return onAbort(s.st.runTransaction(ops), errNotAlive) 834 } 835 836 // Networks returns the networks a service is associated with. Unlike 837 // networks specified with constraints, these networks are required to 838 // be present on machines hosting this service's units. 839 func (s *Service) Networks() ([]string, error) { 840 return readRequestedNetworks(s.st, s.globalKey()) 841 } 842 843 // settingsIncRefOp returns an operation that increments the ref count 844 // of the service settings identified by serviceName and curl. If 845 // canCreate is false, a missing document will be treated as an error; 846 // otherwise, it will be created with a ref count of 1. 847 func settingsIncRefOp(st *State, serviceName string, curl *charm.URL, canCreate bool) (txn.Op, error) { 848 key := serviceSettingsKey(serviceName, curl) 849 if count, err := st.settingsrefs.FindId(key).Count(); err != nil { 850 return txn.Op{}, err 851 } else if count == 0 { 852 if !canCreate { 853 return txn.Op{}, errors.NotFoundf("service settings") 854 } 855 return txn.Op{ 856 C: st.settingsrefs.Name, 857 Id: key, 858 Assert: txn.DocMissing, 859 Insert: settingsRefsDoc{1}, 860 }, nil 861 } 862 return txn.Op{ 863 C: st.settingsrefs.Name, 864 Id: key, 865 Assert: txn.DocExists, 866 Update: bson.D{{"$inc", bson.D{{"refcount", 1}}}}, 867 }, nil 868 } 869 870 // settingsDecRefOps returns a list of operations that decrement the 871 // ref count of the service settings identified by serviceName and 872 // curl. If the ref count is set to zero, the appropriate setting and 873 // ref count documents will both be deleted. 874 func settingsDecRefOps(st *State, serviceName string, curl *charm.URL) ([]txn.Op, error) { 875 key := serviceSettingsKey(serviceName, curl) 876 var doc settingsRefsDoc 877 if err := st.settingsrefs.FindId(key).One(&doc); err == mgo.ErrNotFound { 878 return nil, errors.NotFoundf("service %q settings for charm %q", serviceName, curl) 879 } else if err != nil { 880 return nil, err 881 } 882 if doc.RefCount == 1 { 883 return []txn.Op{{ 884 C: st.settingsrefs.Name, 885 Id: key, 886 Assert: bson.D{{"refcount", 1}}, 887 Remove: true, 888 }, { 889 C: st.settings.Name, 890 Id: key, 891 Remove: true, 892 }}, nil 893 } 894 return []txn.Op{{ 895 C: st.settingsrefs.Name, 896 Id: key, 897 Assert: bson.D{{"refcount", bson.D{{"$gt", 1}}}}, 898 Update: bson.D{{"$inc", bson.D{{"refcount", -1}}}}, 899 }}, nil 900 } 901 902 // settingsRefsDoc holds the number of units and services using the 903 // settings document identified by the document's id. Every time a 904 // service upgrades its charm the settings doc ref count for the new 905 // charm url is incremented, and the old settings is ref count is 906 // decremented. When a unit upgrades to the new charm, the old service 907 // settings ref count is decremented and the ref count of the new 908 // charm settings is incremented. The last unit upgrading to the new 909 // charm is responsible for deleting the old charm's settings doc. 910 // 911 // Note: We're not using the settingsDoc for this because changing 912 // just the ref count is not considered a change worth reporting 913 // to watchers and firing config-changed hooks. 914 // 915 // There is an implicit _id field here, which mongo creates, which is 916 // always the same as the settingsDoc's id. 917 type settingsRefsDoc struct { 918 RefCount int 919 }