~juju-qa/ubuntu/yakkety/juju/juju-1.25.8

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/state/relation.go

  • Committer: Nicholas Skaggs
  • Date: 2016-12-02 17:28:37 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161202172837-jkrbdlyjcxtrii2n
Initial commit of 1.25.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.v5"
 
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
        EnvUUID   string `bson:"env-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 fmt.Errorf("cannot refresh relation %v: %v", r, err)
 
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 fmt.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{}, fmt.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, fmt.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
}