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