~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to state/relation.go

  • Committer: Roger Peppe
  • Date: 2011-12-15 18:54:31 UTC
  • mfrom: (19.5.4 go-juju-ec2-operations)
  • mto: This revision was merged to the branch mainline in revision 30.
  • Revision ID: roger.peppe@canonical.com-20111215185431-tjuxi6bmg1mswcwg
renameĀ environ->environs

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
 
        "labix.org/v2/mgo"
14
 
        "labix.org/v2/mgo/txn"
15
 
 
16
 
        "launchpad.net/juju-core/charm"
17
 
        errors "launchpad.net/juju-core/errors"
18
 
        "launchpad.net/juju-core/utils"
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 state/api/params.
38
 
type relationDoc struct {
39
 
        Key       string `bson:"_id"`
40
 
        Id        int
41
 
        Endpoints []Endpoint
42
 
        Life      Life
43
 
        UnitCount int
44
 
}
45
 
 
46
 
// Relation represents a relation between one or two service endpoints.
47
 
type Relation struct {
48
 
        st  *State
49
 
        doc relationDoc
50
 
}
51
 
 
52
 
func newRelation(st *State, doc *relationDoc) *Relation {
53
 
        return &Relation{
54
 
                st:  st,
55
 
                doc: *doc,
56
 
        }
57
 
}
58
 
 
59
 
func (r *Relation) String() string {
60
 
        return r.doc.Key
61
 
}
62
 
 
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
65
 
// removed.
66
 
func (r *Relation) Refresh() error {
67
 
        doc := relationDoc{}
68
 
        err := r.st.relations.FindId(r.doc.Key).One(&doc)
69
 
        if err == mgo.ErrNotFound {
70
 
                return errors.NotFoundf("relation %v", r)
71
 
        }
72
 
        if err != nil {
73
 
                return fmt.Errorf("cannot refresh relation %v: %v", r, err)
74
 
        }
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)
80
 
        }
81
 
        r.doc = doc
82
 
        return nil
83
 
}
84
 
 
85
 
// Life returns the relation's current life state.
86
 
func (r *Relation) Life() Life {
87
 
        return r.doc.Life
88
 
}
89
 
 
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")
96
 
        }
97
 
        defer func() {
98
 
                if err == nil {
99
 
                        // This is a white lie; the document might actually be removed.
100
 
                        r.doc.Life = Dying
101
 
                }
102
 
        }()
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 {
111
 
                        return nil
112
 
                } else if err != nil {
113
 
                        return err
114
 
                }
115
 
                if err := rel.st.runTransaction(ops); err != txn.ErrAborted {
116
 
                        return err
117
 
                }
118
 
                if err := rel.Refresh(); errors.IsNotFoundError(err) {
119
 
                        return nil
120
 
                } else if err != nil {
121
 
                        return err
122
 
                }
123
 
        }
124
 
        return ErrExcessiveContention
125
 
}
126
 
 
127
 
var errAlreadyDying = stderrors.New("entity is already dying and cannot be destroyed")
128
 
 
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
133
 
// be generated.
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
137
 
        }
138
 
        if r.doc.UnitCount == 0 {
139
 
                removeOps, err := r.removeOps(ignoreService, nil)
140
 
                if err != nil {
141
 
                        return nil, false, err
142
 
                }
143
 
                return removeOps, true, nil
144
 
        }
145
 
        return []txn.Op{{
146
 
                C:      r.st.relations.Name,
147
 
                Id:     r.doc.Key,
148
 
                Assert: D{{"life", Alive}, {"unitcount", D{{"$gt", 0}}}},
149
 
                Update: D{{"$set", D{{"life", Dying}}}},
150
 
        }}, false, nil
151
 
}
152
 
 
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) {
159
 
        relOp := txn.Op{
160
 
                C:      r.st.relations.Name,
161
 
                Id:     r.doc.Key,
162
 
                Remove: true,
163
 
        }
164
 
        if departingUnit != nil {
165
 
                relOp.Assert = D{{"life", Dying}, {"unitcount", 1}}
166
 
        } else {
167
 
                relOp.Assert = D{{"life", Alive}, {"unitcount", 0}}
168
 
        }
169
 
        ops := []txn.Op{relOp}
170
 
        for _, ep := range r.doc.Endpoints {
171
 
                if ep.ServiceName == ignoreService {
172
 
                        continue
173
 
                }
174
 
                var asserts D
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...)
186
 
                } else {
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)...)
193
 
                                continue
194
 
                        } else if err != mgo.ErrNotFound {
195
 
                                return nil, err
196
 
                        }
197
 
                        // If not, we must check that this is still the case when the
198
 
                        // transaction is applied.
199
 
                        asserts = D{{"$or", []D{
200
 
                                {{"life", Alive}},
201
 
                                {{"unitcount", D{{"$gt", 0}}}},
202
 
                                {{"relationcount", D{{"$gt", 1}}}},
203
 
                        }}}
204
 
                }
205
 
                ops = append(ops, txn.Op{
206
 
                        C:      r.st.services.Name,
207
 
                        Id:     ep.ServiceName,
208
 
                        Assert: asserts,
209
 
                        Update: D{{"$inc", D{{"relationcount", -1}}}},
210
 
                })
211
 
        }
212
 
        cleanupOp := r.st.newCleanupOp("settings", fmt.Sprintf("r#%d#", r.Id()))
213
 
        return append(ops, cleanupOp), nil
214
 
}
215
 
 
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 {
221
 
        return r.doc.Id
222
 
}
223
 
 
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 {
229
 
                        return ep, nil
230
 
                }
231
 
        }
232
 
        return Endpoint{}, fmt.Errorf("service %q is not a member of %q", serviceName, r)
233
 
}
234
 
 
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)
240
 
        if err != nil {
241
 
                return nil, err
242
 
        }
243
 
        role := counterpartRole(local.Role)
244
 
        var eps []Endpoint
245
 
        for _, ep := range r.doc.Endpoints {
246
 
                if ep.Role == role {
247
 
                        eps = append(eps, ep)
248
 
                }
249
 
        }
250
 
        if eps == nil {
251
 
                return nil, fmt.Errorf("no endpoints of %q relate to service %q", r, serviceName)
252
 
        }
253
 
        return eps, nil
254
 
}
255
 
 
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)
259
 
        if err != nil {
260
 
                return nil, err
261
 
        }
262
 
        scope := []string{"r", strconv.Itoa(r.doc.Id)}
263
 
        if ep.Scope == charm.ScopeContainer {
264
 
                container := u.doc.Principal
265
 
                if container == "" {
266
 
                        container = u.doc.Name
267
 
                }
268
 
                scope = append(scope, container)
269
 
        }
270
 
        return &RelationUnit{
271
 
                st:       r.st,
272
 
                relation: r,
273
 
                unit:     u,
274
 
                endpoint: ep,
275
 
                scope:    strings.Join(scope, "#"),
276
 
        }, nil
277
 
}