1
// Copyright 2012, 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"labix.org/v2/mgo/txn"
16
"launchpad.net/juju-core/charm"
17
errors "launchpad.net/juju-core/errors"
18
"launchpad.net/juju-core/utils"
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 {
25
for _, ep := range endpoints {
30
for _, ep := range eps {
31
names = append(names, ep.String())
33
return strings.Join(names, " ")
36
// relationDoc is the internal representation of a Relation in MongoDB.
37
// Note the correspondence with RelationInfo in state/api/params.
38
type relationDoc struct {
39
Key string `bson:"_id"`
46
// Relation represents a relation between one or two service endpoints.
47
type Relation struct {
52
func newRelation(st *State, doc *relationDoc) *Relation {
59
func (r *Relation) String() string {
63
// Refresh refreshes the contents of the relation from the underlying
64
// state. It returns an error that satisfies IsNotFound if the relation has been
66
func (r *Relation) Refresh() error {
68
err := r.st.relations.FindId(r.doc.Key).One(&doc)
69
if err == mgo.ErrNotFound {
70
return errors.NotFoundf("relation %v", r)
73
return fmt.Errorf("cannot refresh relation %v: %v", r, err)
75
if r.doc.Id != doc.Id {
76
// The relation has been destroyed and recreated. This is *not* the
77
// same relation; if we pretend it is, we run the risk of violating
78
// the lifecycle-only-advances guarantee.
79
return errors.NotFoundf("relation %v", r)
85
// Life returns the relation's current life state.
86
func (r *Relation) Life() Life {
90
// Destroy ensures that the relation will be removed at some point; if no units
91
// are currently in scope, it will be removed immediately.
92
func (r *Relation) Destroy() (err error) {
93
defer utils.ErrorContextf(&err, "cannot destroy relation %q", r)
94
if len(r.doc.Endpoints) == 1 && r.doc.Endpoints[0].Role == charm.RolePeer {
95
return fmt.Errorf("is a peer relation")
99
// This is a white lie; the document might actually be removed.
103
rel := &Relation{r.st, r.doc}
104
// In this context, aborted transactions indicate that the number of units
105
// in scope have changed between 0 and not-0. The chances of 5 successive
106
// attempts each hitting this change -- which is itself an unlikely one --
107
// are considered to be extremely small.
108
for attempt := 0; attempt < 5; attempt++ {
109
ops, _, err := rel.destroyOps("")
110
if err == errAlreadyDying {
112
} else if err != nil {
115
if err := rel.st.runTransaction(ops); err != txn.ErrAborted {
118
if err := rel.Refresh(); errors.IsNotFoundError(err) {
120
} else if err != nil {
124
return ErrExcessiveContention
127
var errAlreadyDying = stderrors.New("entity is already dying and cannot be destroyed")
129
// destroyOps returns the operations necessary to destroy the relation, and
130
// whether those operations will lead to the relation's removal. These
131
// operations may include changes to the relation's services; however, if
132
// ignoreService is not empty, no operations modifying that service will
134
func (r *Relation) destroyOps(ignoreService string) (ops []txn.Op, isRemove bool, err error) {
135
if r.doc.Life != Alive {
136
return nil, false, errAlreadyDying
138
if r.doc.UnitCount == 0 {
139
removeOps, err := r.removeOps(ignoreService, nil)
141
return nil, false, err
143
return removeOps, true, nil
146
C: r.st.relations.Name,
148
Assert: D{{"life", Alive}, {"unitcount", D{{"$gt", 0}}}},
149
Update: D{{"$set", D{{"life", Dying}}}},
153
// removeOps returns the operations necessary to remove the relation. If
154
// ignoreService is not empty, no operations affecting that service will be
155
// included; if departingUnit is not nil, this implies that the relation's
156
// services may be Dying and otherwise unreferenced, and may thus require
157
// removal themselves.
158
func (r *Relation) removeOps(ignoreService string, departingUnit *Unit) ([]txn.Op, error) {
160
C: r.st.relations.Name,
164
if departingUnit != nil {
165
relOp.Assert = D{{"life", Dying}, {"unitcount", 1}}
167
relOp.Assert = D{{"life", Alive}, {"unitcount", 0}}
169
ops := []txn.Op{relOp}
170
for _, ep := range r.doc.Endpoints {
171
if ep.ServiceName == ignoreService {
175
hasRelation := D{{"relationcount", D{{"$gt", 0}}}}
176
if departingUnit == nil {
177
// We're constructing a destroy operation, either of the relation
178
// or one of its services, and can therefore be assured that both
179
// services are Alive.
180
asserts = append(hasRelation, isAliveDoc...)
181
} else if ep.ServiceName == departingUnit.ServiceName() {
182
// This service must have at least one unit -- the one that's
183
// departing the relation -- so it cannot be ready for removal.
184
cannotDieYet := D{{"unitcount", D{{"$gt", 0}}}}
185
asserts = append(hasRelation, cannotDieYet...)
187
// This service may require immediate removal.
188
svc := &Service{st: r.st}
189
hasLastRef := D{{"life", Dying}, {"unitcount", 0}, {"relationcount", 1}}
190
removable := append(D{{"_id", ep.ServiceName}}, hasLastRef...)
191
if err := r.st.services.Find(removable).One(&svc.doc); err == nil {
192
ops = append(ops, svc.removeOps(hasLastRef)...)
194
} else if err != mgo.ErrNotFound {
197
// If not, we must check that this is still the case when the
198
// transaction is applied.
199
asserts = D{{"$or", []D{
201
{{"unitcount", D{{"$gt", 0}}}},
202
{{"relationcount", D{{"$gt", 1}}}},
205
ops = append(ops, txn.Op{
206
C: r.st.services.Name,
209
Update: D{{"$inc", D{{"relationcount", -1}}}},
212
cleanupOp := r.st.newCleanupOp("settings", fmt.Sprintf("r#%d#", r.Id()))
213
return append(ops, cleanupOp), nil
216
// Id returns the integer internal relation key. This is exposed
217
// because the unit agent needs to expose a value derived from this
218
// (as JUJU_RELATION_ID) to allow relation hooks to differentiate
219
// between relations with different services.
220
func (r *Relation) Id() int {
224
// Endpoint returns the endpoint of the relation for the named service.
225
// If the service is not part of the relation, an error will be returned.
226
func (r *Relation) Endpoint(serviceName string) (Endpoint, error) {
227
for _, ep := range r.doc.Endpoints {
228
if ep.ServiceName == serviceName {
232
return Endpoint{}, fmt.Errorf("service %q is not a member of %q", serviceName, r)
235
// RelatedEndpoints returns the endpoints of the relation r with which
236
// units of the named service will establish relations. If the service
237
// is not part of the relation r, an error will be returned.
238
func (r *Relation) RelatedEndpoints(serviceName string) ([]Endpoint, error) {
239
local, err := r.Endpoint(serviceName)
243
role := counterpartRole(local.Role)
245
for _, ep := range r.doc.Endpoints {
247
eps = append(eps, ep)
251
return nil, fmt.Errorf("no endpoints of %q relate to service %q", r, serviceName)
256
// Unit returns a RelationUnit for the supplied unit.
257
func (r *Relation) Unit(u *Unit) (*RelationUnit, error) {
258
ep, err := r.Endpoint(u.doc.Service)
262
scope := []string{"r", strconv.Itoa(r.doc.Id)}
263
if ep.Scope == charm.ScopeContainer {
264
container := u.doc.Principal
266
container = u.doc.Name
268
scope = append(scope, container)
270
return &RelationUnit{
275
scope: strings.Join(scope, "#"),