~juju-qa/ubuntu/trusty/juju/juju-1.25.8

« back to all changes in this revision

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

  • Committer: Nicholas Skaggs
  • Date: 2016-12-02 18:01:10 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161202180110-dl1helep8qfebmhx
ImportĀ upstreamĀ 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
        "github.com/juju/errors"
 
8
        jujutxn "github.com/juju/txn"
 
9
        "gopkg.in/mgo.v2/bson"
 
10
        "gopkg.in/mgo.v2/txn"
 
11
)
 
12
 
 
13
// minUnitsDoc keeps track of relevant changes on the service's MinUnits field
 
14
// and on the number of alive units for the service.
 
15
// A new document is created when MinUnits is set to a non zero value.
 
16
// A document is deleted when either the associated service is destroyed
 
17
// or MinUnits is restored to zero. The Revno is increased when either MinUnits
 
18
// for a service is increased or a unit is destroyed.
 
19
// TODO(frankban): the MinUnitsWatcher reacts to changes by sending events,
 
20
// each one describing one or more services. A worker reacts to those events
 
21
// ensuring the number of units for the service is never less than the actual
 
22
// alive units: new units are added if required.
 
23
type minUnitsDoc struct {
 
24
        // ServiceName is safe to be used here in place of its globalKey, since
 
25
        // the referred entity type is always the Service.
 
26
        DocID       string `bson:"_id"`
 
27
        ServiceName string
 
28
        EnvUUID     string `bson:"env-uuid"`
 
29
        Revno       int
 
30
}
 
31
 
 
32
// SetMinUnits changes the number of minimum units required by the service.
 
33
func (s *Service) SetMinUnits(minUnits int) (err error) {
 
34
        defer errors.DeferredAnnotatef(&err, "cannot set minimum units for service %q", s)
 
35
        defer func() {
 
36
                if err == nil {
 
37
                        s.doc.MinUnits = minUnits
 
38
                }
 
39
        }()
 
40
        if minUnits < 0 {
 
41
                return errors.New("cannot set a negative minimum number of units")
 
42
        }
 
43
        service := &Service{st: s.st, doc: s.doc}
 
44
        // Removing the document never fails. Racing clients trying to create the
 
45
        // document generate one failure, but the second attempt should succeed.
 
46
        // If one client tries to update the document, and a racing client removes
 
47
        // it, the former should be able to re-create the document in the second
 
48
        // attempt. If the referred-to service advanced its life cycle to a not
 
49
        // alive state, an error is returned after the first failing attempt.
 
50
        buildTxn := func(attempt int) ([]txn.Op, error) {
 
51
                if attempt > 0 {
 
52
                        if err := service.Refresh(); err != nil {
 
53
                                return nil, err
 
54
                        }
 
55
                }
 
56
                if service.doc.Life != Alive {
 
57
                        return nil, errors.New("service is no longer alive")
 
58
                }
 
59
                if minUnits == service.doc.MinUnits {
 
60
                        return nil, jujutxn.ErrNoOperations
 
61
                }
 
62
                return setMinUnitsOps(service, minUnits), nil
 
63
        }
 
64
        return s.st.run(buildTxn)
 
65
}
 
66
 
 
67
// setMinUnitsOps returns the operations required to set MinUnits on the
 
68
// service and to create/update/remove the minUnits document in MongoDB.
 
69
func setMinUnitsOps(service *Service, minUnits int) []txn.Op {
 
70
        state := service.st
 
71
        serviceName := service.Name()
 
72
        ops := []txn.Op{{
 
73
                C:      servicesC,
 
74
                Id:     state.docID(serviceName),
 
75
                Assert: isAliveDoc,
 
76
                Update: bson.D{{"$set", bson.D{{"minunits", minUnits}}}},
 
77
        }}
 
78
        if service.doc.MinUnits == 0 {
 
79
                return append(ops, txn.Op{
 
80
                        C:      minUnitsC,
 
81
                        Id:     state.docID(serviceName),
 
82
                        Assert: txn.DocMissing,
 
83
                        Insert: &minUnitsDoc{
 
84
                                ServiceName: serviceName,
 
85
                                EnvUUID:     service.st.EnvironUUID(),
 
86
                        },
 
87
                })
 
88
        }
 
89
        if minUnits == 0 {
 
90
                return append(ops, minUnitsRemoveOp(state, serviceName))
 
91
        }
 
92
        if minUnits > service.doc.MinUnits {
 
93
                op := minUnitsTriggerOp(state, serviceName)
 
94
                op.Assert = txn.DocExists
 
95
                return append(ops, op)
 
96
        }
 
97
        return ops
 
98
}
 
99
 
 
100
// minUnitsTriggerOp returns the operation required to increase the minimum
 
101
// units revno for the service in MongoDB, ignoring the case of document not
 
102
// existing. This is included in the operations performed when a unit is
 
103
// destroyed: if the document exists, then we need to update the Revno.
 
104
// If the service does not require a minimum number of units, then the
 
105
// operation is a noop.
 
106
func minUnitsTriggerOp(st *State, serviceName string) txn.Op {
 
107
        return txn.Op{
 
108
                C:      minUnitsC,
 
109
                Id:     st.docID(serviceName),
 
110
                Update: bson.D{{"$inc", bson.D{{"revno", 1}}}},
 
111
        }
 
112
}
 
113
 
 
114
// minUnitsRemoveOp returns the operation required to remove the minimum
 
115
// units document from MongoDB.
 
116
func minUnitsRemoveOp(st *State, serviceName string) txn.Op {
 
117
        return txn.Op{
 
118
                C:      minUnitsC,
 
119
                Id:     st.docID(serviceName),
 
120
                Remove: true,
 
121
        }
 
122
}
 
123
 
 
124
// MinUnits returns the minimum units count for the service.
 
125
func (s *Service) MinUnits() int {
 
126
        return s.doc.MinUnits
 
127
}
 
128
 
 
129
// EnsureMinUnits adds new units if the service's MinUnits value is greater
 
130
// than the number of alive units.
 
131
func (s *Service) EnsureMinUnits() (err error) {
 
132
        defer errors.DeferredAnnotatef(&err, "cannot ensure minimum units for service %q", s)
 
133
        service := &Service{st: s.st, doc: s.doc}
 
134
        for {
 
135
                // Ensure the service is alive.
 
136
                if service.doc.Life != Alive {
 
137
                        return errors.New("service is not alive")
 
138
                }
 
139
                // Exit without errors if the MinUnits for the service is not set.
 
140
                if service.doc.MinUnits == 0 {
 
141
                        return nil
 
142
                }
 
143
                // Retrieve the number of alive units for the service.
 
144
                aliveUnits, err := aliveUnitsCount(service)
 
145
                if err != nil {
 
146
                        return err
 
147
                }
 
148
                // Calculate the number of required units to be added.
 
149
                missing := service.doc.MinUnits - aliveUnits
 
150
                if missing <= 0 {
 
151
                        return nil
 
152
                }
 
153
                name, ops, err := ensureMinUnitsOps(service)
 
154
                if err != nil {
 
155
                        return err
 
156
                }
 
157
                // Add missing unit.
 
158
                switch err := s.st.runTransaction(ops); err {
 
159
                case nil:
 
160
                        // Assign the new unit.
 
161
                        unit, err := s.st.Unit(name)
 
162
                        if err != nil {
 
163
                                return err
 
164
                        }
 
165
                        if err := service.st.AssignUnit(unit, AssignNew); err != nil {
 
166
                                return err
 
167
                        }
 
168
                        // No need to proceed and refresh the service if this was the
 
169
                        // last/only missing unit.
 
170
                        if missing == 1 {
 
171
                                return nil
 
172
                        }
 
173
                case txn.ErrAborted:
 
174
                        // Refresh the service and restart the loop.
 
175
                default:
 
176
                        return err
 
177
                }
 
178
                if err := service.Refresh(); err != nil {
 
179
                        return err
 
180
                }
 
181
        }
 
182
}
 
183
 
 
184
// aliveUnitsCount returns the number a alive units for the service.
 
185
func aliveUnitsCount(service *Service) (int, error) {
 
186
        units, closer := service.st.getCollection(unitsC)
 
187
        defer closer()
 
188
 
 
189
        query := bson.D{{"service", service.doc.Name}, {"life", Alive}}
 
190
        return units.Find(query).Count()
 
191
}
 
192
 
 
193
// ensureMinUnitsOps returns the operations required to add a unit for the
 
194
// service in MongoDB and the name for the new unit. The resulting transaction
 
195
// will be aborted if the service document changes when running the operations.
 
196
func ensureMinUnitsOps(service *Service) (string, []txn.Op, error) {
 
197
        asserts := bson.D{{"txn-revno", service.doc.TxnRevno}}
 
198
        return service.addUnitOps("", asserts)
 
199
}