github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/minimumunits.go (about) 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 application. 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 application is never less than the actual 22 // alive units: new units are added if required. 23 type minUnitsDoc struct { 24 // ApplicationName 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 ApplicationName string 28 ModelUUID string `bson:"model-uuid"` 29 Revno int 30 } 31 32 // SetMinUnits changes the number of minimum units required by the service. 33 func (s *Application) SetMinUnits(minUnits int) (err error) { 34 defer errors.DeferredAnnotatef(&err, "cannot set minimum units for application %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 := &Application{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("application 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 *Application, minUnits int) []txn.Op { 70 state := service.st 71 applicationname := service.Name() 72 ops := []txn.Op{{ 73 C: applicationsC, 74 Id: state.docID(applicationname), 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(applicationname), 82 Assert: txn.DocMissing, 83 Insert: &minUnitsDoc{ 84 ApplicationName: applicationname, 85 ModelUUID: service.st.ModelUUID(), 86 }, 87 }) 88 } 89 if minUnits == 0 { 90 return append(ops, minUnitsRemoveOp(state, applicationname)) 91 } 92 if minUnits > service.doc.MinUnits { 93 op := minUnitsTriggerOp(state, applicationname) 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 application 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, applicationname string) txn.Op { 107 return txn.Op{ 108 C: minUnitsC, 109 Id: st.docID(applicationname), 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, applicationname string) txn.Op { 117 return txn.Op{ 118 C: minUnitsC, 119 Id: st.docID(applicationname), 120 Remove: true, 121 } 122 } 123 124 // MinUnits returns the minimum units count for the application. 125 func (s *Application) 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 *Application) EnsureMinUnits() (err error) { 132 defer errors.DeferredAnnotatef(&err, "cannot ensure minimum units for application %q", s) 133 service := &Application{st: s.st, doc: s.doc} 134 for { 135 // Ensure the service is alive. 136 if service.doc.Life != Alive { 137 return errors.New("application is not alive") 138 } 139 // Exit without errors if the MinUnits for the application is not set. 140 if service.doc.MinUnits == 0 { 141 return nil 142 } 143 // Retrieve the number of alive units for the application. 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 application. 185 func aliveUnitsCount(service *Application) (int, error) { 186 units, closer := service.st.getCollection(unitsC) 187 defer closer() 188 189 query := bson.D{{"application", 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 *Application) (string, []txn.Op, error) { 197 asserts := bson.D{{"txn-revno", service.doc.TxnRevno}} 198 return service.addUnitOps("", asserts) 199 }