github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/relation.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 "sort" 9 "strconv" 10 "strings" 11 12 "github.com/juju/errors" 13 jujutxn "github.com/juju/txn" 14 "gopkg.in/juju/charm.v6-unstable" 15 "gopkg.in/juju/names.v2" 16 "gopkg.in/mgo.v2" 17 "gopkg.in/mgo.v2/bson" 18 "gopkg.in/mgo.v2/txn" 19 ) 20 21 // relationKey returns a string describing the relation defined by 22 // endpoints, for use in various contexts (including error messages). 23 func relationKey(endpoints []Endpoint) string { 24 eps := epSlice{} 25 for _, ep := range endpoints { 26 eps = append(eps, ep) 27 } 28 sort.Sort(eps) 29 names := []string{} 30 for _, ep := range eps { 31 names = append(names, ep.String()) 32 } 33 return strings.Join(names, " ") 34 } 35 36 // relationDoc is the internal representation of a Relation in MongoDB. 37 // Note the correspondence with RelationInfo in apiserver/params. 38 type relationDoc struct { 39 DocID string `bson:"_id"` 40 Key string `bson:"key"` 41 ModelUUID string `bson:"model-uuid"` 42 Id int 43 Endpoints []Endpoint 44 Life Life 45 UnitCount int 46 } 47 48 // Relation represents a relation between one or two service endpoints. 49 type Relation struct { 50 st *State 51 doc relationDoc 52 } 53 54 func newRelation(st *State, doc *relationDoc) *Relation { 55 return &Relation{ 56 st: st, 57 doc: *doc, 58 } 59 } 60 61 func (r *Relation) String() string { 62 return r.doc.Key 63 } 64 65 // Tag returns a name identifying the relation. 66 func (r *Relation) Tag() names.Tag { 67 return names.NewRelationTag(r.doc.Key) 68 } 69 70 // Refresh refreshes the contents of the relation from the underlying 71 // state. It returns an error that satisfies errors.IsNotFound if the 72 // relation has been removed. 73 func (r *Relation) Refresh() error { 74 relations, closer := r.st.getCollection(relationsC) 75 defer closer() 76 77 doc := relationDoc{} 78 err := relations.FindId(r.doc.DocID).One(&doc) 79 if err == mgo.ErrNotFound { 80 return errors.NotFoundf("relation %v", r) 81 } 82 if err != nil { 83 return errors.Annotatef(err, "cannot refresh relation %v", r) 84 } 85 if r.doc.Id != doc.Id { 86 // The relation has been destroyed and recreated. This is *not* the 87 // same relation; if we pretend it is, we run the risk of violating 88 // the lifecycle-only-advances guarantee. 89 return errors.NotFoundf("relation %v", r) 90 } 91 r.doc = doc 92 return nil 93 } 94 95 // Life returns the relation's current life state. 96 func (r *Relation) Life() Life { 97 return r.doc.Life 98 } 99 100 // Destroy ensures that the relation will be removed at some point; if no units 101 // are currently in scope, it will be removed immediately. 102 func (r *Relation) Destroy() (err error) { 103 defer errors.DeferredAnnotatef(&err, "cannot destroy relation %q", r) 104 if len(r.doc.Endpoints) == 1 && r.doc.Endpoints[0].Role == charm.RolePeer { 105 return errors.Errorf("is a peer relation") 106 } 107 defer func() { 108 if err == nil { 109 // This is a white lie; the document might actually be removed. 110 r.doc.Life = Dying 111 } 112 }() 113 rel := &Relation{r.st, r.doc} 114 // In this context, aborted transactions indicate that the number of units 115 // in scope have changed between 0 and not-0. The chances of 5 successive 116 // attempts each hitting this change -- which is itself an unlikely one -- 117 // are considered to be extremely small. 118 buildTxn := func(attempt int) ([]txn.Op, error) { 119 if attempt > 0 { 120 if err := rel.Refresh(); errors.IsNotFound(err) { 121 return []txn.Op{}, nil 122 } else if err != nil { 123 return nil, err 124 } 125 } 126 ops, _, err := rel.destroyOps("") 127 if err == errAlreadyDying { 128 return nil, jujutxn.ErrNoOperations 129 } else if err != nil { 130 return nil, err 131 } 132 return ops, nil 133 } 134 return rel.st.run(buildTxn) 135 } 136 137 // destroyOps returns the operations necessary to destroy the relation, and 138 // whether those operations will lead to the relation's removal. These 139 // operations may include changes to the relation's services; however, if 140 // ignoreService is not empty, no operations modifying that service will 141 // be generated. 142 func (r *Relation) destroyOps(ignoreService string) (ops []txn.Op, isRemove bool, err error) { 143 if r.doc.Life != Alive { 144 return nil, false, errAlreadyDying 145 } 146 if r.doc.UnitCount == 0 { 147 removeOps, err := r.removeOps(ignoreService, nil) 148 if err != nil { 149 return nil, false, err 150 } 151 return removeOps, true, nil 152 } 153 return []txn.Op{{ 154 C: relationsC, 155 Id: r.doc.DocID, 156 Assert: bson.D{{"life", Alive}, {"unitcount", bson.D{{"$gt", 0}}}}, 157 Update: bson.D{{"$set", bson.D{{"life", Dying}}}}, 158 }}, false, nil 159 } 160 161 // removeOps returns the operations necessary to remove the relation. If 162 // ignoreService is not empty, no operations affecting that service will be 163 // included; if departingUnit is not nil, this implies that the relation's 164 // services may be Dying and otherwise unreferenced, and may thus require 165 // removal themselves. 166 func (r *Relation) removeOps(ignoreService string, departingUnit *Unit) ([]txn.Op, error) { 167 relOp := txn.Op{ 168 C: relationsC, 169 Id: r.doc.DocID, 170 Remove: true, 171 } 172 if departingUnit != nil { 173 relOp.Assert = bson.D{{"life", Dying}, {"unitcount", 1}} 174 } else { 175 relOp.Assert = bson.D{{"life", Alive}, {"unitcount", 0}} 176 } 177 ops := []txn.Op{relOp} 178 for _, ep := range r.doc.Endpoints { 179 if ep.ApplicationName == ignoreService { 180 continue 181 } 182 var asserts bson.D 183 hasRelation := bson.D{{"relationcount", bson.D{{"$gt", 0}}}} 184 if departingUnit == nil { 185 // We're constructing a destroy operation, either of the relation 186 // or one of its services, and can therefore be assured that both 187 // services are Alive. 188 asserts = append(hasRelation, isAliveDoc...) 189 } else if ep.ApplicationName == departingUnit.ApplicationName() { 190 // This service must have at least one unit -- the one that's 191 // departing the relation -- so it cannot be ready for removal. 192 cannotDieYet := bson.D{{"unitcount", bson.D{{"$gt", 0}}}} 193 asserts = append(hasRelation, cannotDieYet...) 194 } else { 195 // This service may require immediate removal. 196 applications, closer := r.st.getCollection(applicationsC) 197 defer closer() 198 199 svc := &Application{st: r.st} 200 hasLastRef := bson.D{{"life", Dying}, {"unitcount", 0}, {"relationcount", 1}} 201 removable := append(bson.D{{"_id", ep.ApplicationName}}, hasLastRef...) 202 if err := applications.Find(removable).One(&svc.doc); err == nil { 203 appRemoveOps, err := svc.removeOps(hasLastRef) 204 if err != nil { 205 return nil, errors.Trace(err) 206 } 207 ops = append(ops, appRemoveOps...) 208 continue 209 } else if err != mgo.ErrNotFound { 210 return nil, err 211 } 212 // If not, we must check that this is still the case when the 213 // transaction is applied. 214 asserts = bson.D{{"$or", []bson.D{ 215 {{"life", Alive}}, 216 {{"unitcount", bson.D{{"$gt", 0}}}}, 217 {{"relationcount", bson.D{{"$gt", 1}}}}, 218 }}} 219 } 220 ops = append(ops, txn.Op{ 221 C: applicationsC, 222 Id: r.st.docID(ep.ApplicationName), 223 Assert: asserts, 224 Update: bson.D{{"$inc", bson.D{{"relationcount", -1}}}}, 225 }) 226 } 227 cleanupOp := newCleanupOp(cleanupRelationSettings, fmt.Sprintf("r#%d#", r.Id())) 228 return append(ops, cleanupOp), nil 229 } 230 231 // Id returns the integer internal relation key. This is exposed 232 // because the unit agent needs to expose a value derived from this 233 // (as JUJU_RELATION_ID) to allow relation hooks to differentiate 234 // between relations with different services. 235 func (r *Relation) Id() int { 236 return r.doc.Id 237 } 238 239 // Endpoint returns the endpoint of the relation for the named service. 240 // If the service is not part of the relation, an error will be returned. 241 func (r *Relation) Endpoint(applicationname string) (Endpoint, error) { 242 for _, ep := range r.doc.Endpoints { 243 if ep.ApplicationName == applicationname { 244 return ep, nil 245 } 246 } 247 return Endpoint{}, errors.Errorf("application %q is not a member of %q", applicationname, r) 248 } 249 250 // Endpoints returns the endpoints for the relation. 251 func (r *Relation) Endpoints() []Endpoint { 252 return r.doc.Endpoints 253 } 254 255 // RelatedEndpoints returns the endpoints of the relation r with which 256 // units of the named service will establish relations. If the service 257 // is not part of the relation r, an error will be returned. 258 func (r *Relation) RelatedEndpoints(applicationname string) ([]Endpoint, error) { 259 local, err := r.Endpoint(applicationname) 260 if err != nil { 261 return nil, err 262 } 263 role := counterpartRole(local.Role) 264 var eps []Endpoint 265 for _, ep := range r.doc.Endpoints { 266 if ep.Role == role { 267 eps = append(eps, ep) 268 } 269 } 270 if eps == nil { 271 return nil, errors.Errorf("no endpoints of %q relate to application %q", r, applicationname) 272 } 273 return eps, nil 274 } 275 276 // Unit returns a RelationUnit for the supplied unit. 277 func (r *Relation) Unit(u *Unit) (*RelationUnit, error) { 278 ep, err := r.Endpoint(u.doc.Application) 279 if err != nil { 280 return nil, err 281 } 282 scope := []string{"r", strconv.Itoa(r.doc.Id)} 283 if ep.Scope == charm.ScopeContainer { 284 container := u.doc.Principal 285 if container == "" { 286 container = u.doc.Name 287 } 288 scope = append(scope, container) 289 } 290 return &RelationUnit{ 291 st: r.st, 292 relation: r, 293 unit: u, 294 endpoint: ep, 295 scope: strings.Join(scope, "#"), 296 }, nil 297 } 298 299 // relationSettingsCleanupChange removes the settings doc. 300 type relationSettingsCleanupChange struct { 301 Prefix string 302 } 303 304 // Prepare is part of the Change interface. 305 func (change relationSettingsCleanupChange) Prepare(db Database) ([]txn.Op, error) { 306 settings, closer := db.GetCollection(settingsC) 307 defer closer() 308 sel := bson.D{{"_id", bson.D{{"$regex", "^" + change.Prefix}}}} 309 var docs []struct { 310 DocID string `bson:"_id"` 311 } 312 err := settings.Find(sel).Select(bson.D{{"_id", 1}}).All(&docs) 313 if err != nil { 314 return nil, errors.Trace(err) 315 } 316 if len(docs) == 0 { 317 return nil, ErrChangeComplete 318 } 319 320 ops := make([]txn.Op, len(docs)) 321 for i, doc := range docs { 322 ops[i] = txn.Op{ 323 C: settingsC, 324 Id: doc.DocID, 325 Remove: true, 326 } 327 } 328 return ops, nil 329 330 }