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