1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
1 |
// Copyright 2012, 2013 Canonical Ltd.
|
2 |
// Licensed under the AGPLv3, see LICENCE file for details.
|
|
3 |
||
4 |
package state |
|
5 |
||
6 |
import ( |
|
1318.1.6
by Francesco Banconi
Completed tests. |
7 |
"errors"
|
1408.1.1
by John Arbash Meinel
Start changing the imports of the middle level files. |
8 |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
9 |
"labix.org/v2/mgo/txn"
|
1397.5.14
by Francesco Banconi
Remove debug commands. |
10 |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
11 |
"launchpad.net/juju-core/utils"
|
12 |
)
|
|
13 |
||
1318.1.16
by Francesco Banconi
Changes as per review. |
14 |
// minUnitsDoc keeps track of relevant changes on the service's MinUnits field
|
15 |
// and on the number of alive units for the service.
|
|
16 |
// A new document is created when MinUnits is set to a non zero value.
|
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
17 |
// A document is deleted when either the associated service is destroyed
|
1318.1.16
by Francesco Banconi
Changes as per review. |
18 |
// or MinUnits is restored to zero. The Revno is increased when either MinUnits
|
19 |
// for a service is increased or a unit is destroyed.
|
|
20 |
// TODO(frankban): the MinUnitsWatcher reacts to changes by sending events,
|
|
1318.1.6
by Francesco Banconi
Completed tests. |
21 |
// each one describing one or more services. A worker reacts to those events
|
22 |
// ensuring the number of units for the service is never less than the actual
|
|
1318.1.16
by Francesco Banconi
Changes as per review. |
23 |
// alive units: new units are added if required.
|
24 |
type minUnitsDoc struct { |
|
1318.1.13
by Francesco Banconi
Comment changes as per review. |
25 |
// ServiceName is safe to be used here in place of its globalKey, since
|
26 |
// the referred entity type is always the Service.
|
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
27 |
ServiceName string `bson:"_id"` |
1318.1.6
by Francesco Banconi
Completed tests. |
28 |
Revno int |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
29 |
}
|
30 |
||
1318.1.16
by Francesco Banconi
Changes as per review. |
31 |
// SetMinUnits changes the number of minimum units required by the service.
|
32 |
func (s *Service) SetMinUnits(minUnits int) (err error) { |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
33 |
defer utils.ErrorContextf(&err, "cannot set minimum units for service %q", s) |
1318.1.16
by Francesco Banconi
Changes as per review. |
34 |
defer func() { |
35 |
if err == nil { |
|
36 |
s.doc.MinUnits = minUnits |
|
37 |
}
|
|
38 |
}()
|
|
39 |
if minUnits < 0 { |
|
40 |
return errors.New("cannot set a negative minimum number of units") |
|
41 |
}
|
|
42 |
service := &Service{st: s.st, doc: s.doc} |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
43 |
// Removing the document never fails. Racing clients trying to create the
|
44 |
// document generate one failure, but the second attempt should succeed.
|
|
1318.1.6
by Francesco Banconi
Completed tests. |
45 |
// If one client tries to update the document, and a racing client removes
|
46 |
// it, the former should be able to re-create the document in the second
|
|
1318.1.14
by Francesco Banconi
Changes as per review. |
47 |
// attempt. If the referred-to service advanced its life cycle to a not
|
1318.1.16
by Francesco Banconi
Changes as per review. |
48 |
// alive state, an error is returned after the first failing attempt.
|
49 |
for i := 0; i < 2; i++ { |
|
50 |
if service.doc.Life != Alive { |
|
51 |
return errors.New("service is no longer alive") |
|
52 |
}
|
|
53 |
if minUnits == service.doc.MinUnits { |
|
54 |
return nil |
|
55 |
}
|
|
56 |
ops := setMinUnitsOps(service, minUnits) |
|
1318.1.21
by Francesco Banconi
Changes as per review. |
57 |
if err := s.st.runTransaction(ops); err != txn.ErrAborted { |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
58 |
return err |
59 |
}
|
|
1318.1.16
by Francesco Banconi
Changes as per review. |
60 |
if err := service.Refresh(); err != nil { |
61 |
return err |
|
62 |
}
|
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
63 |
}
|
64 |
return ErrExcessiveContention |
|
65 |
}
|
|
66 |
||
1318.1.18
by Francesco Banconi
Checkpoint. |
67 |
// setMinUnitsOps returns the operations required to set MinUnits on the
|
68 |
// service and to create/update/remove the minUnits document in MongoDB.
|
|
1318.1.16
by Francesco Banconi
Changes as per review. |
69 |
func setMinUnitsOps(service *Service, minUnits int) []txn.Op { |
70 |
state := service.st |
|
71 |
serviceName := service.Name() |
|
72 |
ops := []txn.Op{{ |
|
73 |
C: state.services.Name, |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
74 |
Id: serviceName, |
1318.1.16
by Francesco Banconi
Changes as per review. |
75 |
Assert: isAliveDoc, |
76 |
Update: D{{"$set", D{{"minunits", minUnits}}}}, |
|
77 |
}}
|
|
78 |
if service.doc.MinUnits == 0 { |
|
79 |
return append(ops, txn.Op{ |
|
80 |
C: state.minUnits.Name, |
|
81 |
Id: serviceName, |
|
82 |
Assert: txn.DocMissing, |
|
83 |
Insert: &minUnitsDoc{ServiceName: serviceName}, |
|
84 |
})
|
|
85 |
}
|
|
86 |
if minUnits == 0 { |
|
87 |
return append(ops, minUnitsRemoveOp(state, serviceName)) |
|
88 |
}
|
|
89 |
if minUnits > service.doc.MinUnits { |
|
1318.1.21
by Francesco Banconi
Changes as per review. |
90 |
op := minUnitsTriggerOp(state, serviceName) |
1318.1.16
by Francesco Banconi
Changes as per review. |
91 |
op.Assert = txn.DocExists |
92 |
return append(ops, op) |
|
93 |
}
|
|
94 |
return ops |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
95 |
}
|
96 |
||
1318.1.21
by Francesco Banconi
Changes as per review. |
97 |
// minUnitsTriggerOp returns the operation required to increase the minimum
|
1318.1.16
by Francesco Banconi
Changes as per review. |
98 |
// units revno for the service in MongoDB, ignoring the case of document not
|
99 |
// existing. This is included in the operations performed when a unit is
|
|
100 |
// destroyed: if the document exists, then we need to update the Revno.
|
|
101 |
// If the service does not require a minimum number of units, then the
|
|
102 |
// operation is a noop.
|
|
1318.1.21
by Francesco Banconi
Changes as per review. |
103 |
func minUnitsTriggerOp(st *State, serviceName string) txn.Op { |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
104 |
return txn.Op{ |
1318.1.16
by Francesco Banconi
Changes as per review. |
105 |
C: st.minUnits.Name, |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
106 |
Id: serviceName, |
107 |
Update: D{{"$inc", D{{"revno", 1}}}}, |
|
108 |
}
|
|
109 |
}
|
|
110 |
||
1318.1.16
by Francesco Banconi
Changes as per review. |
111 |
// minUnitsRemoveOp returns the operation required to remove the minimum
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
112 |
// units document from MongoDB.
|
1318.1.16
by Francesco Banconi
Changes as per review. |
113 |
func minUnitsRemoveOp(st *State, serviceName string) txn.Op { |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
114 |
return txn.Op{ |
1318.1.16
by Francesco Banconi
Changes as per review. |
115 |
C: st.minUnits.Name, |
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
116 |
Id: serviceName, |
117 |
Remove: true, |
|
118 |
}
|
|
119 |
}
|
|
120 |
||
1318.1.16
by Francesco Banconi
Changes as per review. |
121 |
// MinUnits returns the minimum units count for the service.
|
122 |
func (s *Service) MinUnits() int { |
|
123 |
return s.doc.MinUnits |
|
1318.1.2
by Francesco Banconi
Implemented SetMinimumUnits and related collection. |
124 |
}
|
1397.5.1
by Francesco Banconi
Checkpoint. |
125 |
|
126 |
// EnsureMinUnits adds new units if the service's MinUnits value is greater
|
|
127 |
// than the number of alive units.
|
|
128 |
func (s *Service) EnsureMinUnits() (err error) { |
|
1397.5.20
by Francesco Banconi
Changes as per review. |
129 |
defer utils.ErrorContextf(&err, "cannot ensure minimum units for service %q", s) |
1397.5.3
by Francesco Banconi
Refactoring. |
130 |
service := &Service{st: s.st, doc: s.doc} |
1397.5.1
by Francesco Banconi
Checkpoint. |
131 |
for { |
132 |
// Ensure the service is alive.
|
|
1397.5.3
by Francesco Banconi
Refactoring. |
133 |
if service.doc.Life != Alive { |
1397.5.17
by Francesco Banconi
Changes as per review. |
134 |
return errors.New("service is not alive") |
1397.5.1
by Francesco Banconi
Checkpoint. |
135 |
}
|
136 |
// Exit without errors if the MinUnits for the service is not set.
|
|
1397.5.3
by Francesco Banconi
Refactoring. |
137 |
if service.doc.MinUnits == 0 { |
1397.5.1
by Francesco Banconi
Checkpoint. |
138 |
return nil |
139 |
}
|
|
140 |
// Retrieve the number of alive units for the service.
|
|
1397.5.3
by Francesco Banconi
Refactoring. |
141 |
aliveUnits, err := aliveUnitsCount(service) |
1397.5.1
by Francesco Banconi
Checkpoint. |
142 |
if err != nil { |
143 |
return err |
|
144 |
}
|
|
145 |
// Calculate the number of required units to be added.
|
|
1397.5.3
by Francesco Banconi
Refactoring. |
146 |
missing := service.doc.MinUnits - aliveUnits |
1397.5.1
by Francesco Banconi
Checkpoint. |
147 |
if missing <= 0 { |
148 |
return nil |
|
149 |
}
|
|
1397.5.10
by Francesco Banconi
Checkpoint. |
150 |
name, ops, err := ensureMinUnitsOps(service) |
151 |
if err != nil { |
|
152 |
return err |
|
153 |
}
|
|
154 |
// Add missing unit.
|
|
155 |
switch err := s.st.runTransaction(ops); err { |
|
156 |
case nil: |
|
157 |
// Assign the new unit.
|
|
1397.5.3
by Francesco Banconi
Refactoring. |
158 |
unit, err := service.Unit(name) |
159 |
if err != nil { |
|
160 |
return err |
|
161 |
}
|
|
162 |
if err := service.st.AssignUnit(unit, AssignNew); err != nil { |
|
1397.5.1
by Francesco Banconi
Checkpoint. |
163 |
return err |
164 |
}
|
|
1397.5.10
by Francesco Banconi
Checkpoint. |
165 |
// No need to proceed and refresh the service if this was the
|
166 |
// last/only missing unit.
|
|
167 |
if missing == 1 { |
|
168 |
return nil |
|
169 |
}
|
|
170 |
case txn.ErrAborted: |
|
1397.5.20
by Francesco Banconi
Changes as per review. |
171 |
// Refresh the service and restart the loop.
|
1397.5.10
by Francesco Banconi
Checkpoint. |
172 |
default: |
173 |
return err |
|
174 |
}
|
|
175 |
if err := service.Refresh(); err != nil { |
|
176 |
return err |
|
177 |
}
|
|
1397.5.1
by Francesco Banconi
Checkpoint. |
178 |
}
|
179 |
}
|
|
180 |
||
181 |
// aliveUnitsCount returns the number a alive units for the service.
|
|
182 |
func aliveUnitsCount(service *Service) (int, error) { |
|
183 |
query := D{{"service", service.doc.Name}, {"life", Alive}} |
|
1397.5.4
by Francesco Banconi
Fix documentation comments. |
184 |
return service.st.units.Find(query).Count() |
1397.5.1
by Francesco Banconi
Checkpoint. |
185 |
}
|
186 |
||
187 |
// ensureMinUnitsOps returns the operations required to add a unit for the
|
|
1397.5.4
by Francesco Banconi
Fix documentation comments. |
188 |
// service in MongoDB and the name for the new unit. The resulting transaction
|
189 |
// will be aborted if the service document changes when running the operations.
|
|
1397.5.1
by Francesco Banconi
Checkpoint. |
190 |
func ensureMinUnitsOps(service *Service) (string, []txn.Op, error) { |
1397.5.15
by Francesco Banconi
Ready to propose. |
191 |
asserts := D{{"txn-revno", service.doc.TxnRevno}} |
192 |
return service.addUnitOps("", asserts) |
|
1397.5.1
by Francesco Banconi
Checkpoint. |
193 |
}
|