~themue/juju-core/053-env-more-script-friendly

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
}