github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/payloads.go (about) 1 // Copyright 2015 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 "gopkg.in/mgo.v2/bson" 9 "gopkg.in/mgo.v2/txn" 10 11 "github.com/juju/juju/payload" 12 ) 13 14 // ModelPayloads returns a ModelPayloads for the state's model. 15 func (st *State) ModelPayloads() (ModelPayloads, error) { 16 return ModelPayloads{ 17 db: st.database, 18 }, nil 19 } 20 21 // ModelPayloads lets you read all unit payloads in a model. 22 type ModelPayloads struct { 23 db Database 24 } 25 26 // ListAll builds the list of payload information that is registered in state. 27 func (mp ModelPayloads) ListAll() ([]payload.FullPayloadInfo, error) { 28 coll, closer := mp.db.GetCollection(payloadsC) 29 defer closer() 30 31 var docs []payloadDoc 32 if err := coll.Find(nil).All(&docs); err != nil { 33 return nil, errors.Trace(err) 34 } 35 return nsPayloads.asPayloads(docs), nil 36 } 37 38 // UnitPayloads returns a UnitPayloads for the supplied unit. 39 func (st *State) UnitPayloads(unit *Unit) (UnitPayloads, error) { 40 machineID, err := unit.AssignedMachineId() 41 if err != nil { 42 return UnitPayloads{}, errors.Trace(err) 43 } 44 return UnitPayloads{ 45 db: st.database, 46 unit: unit.Name(), 47 machine: machineID, 48 }, nil 49 } 50 51 // UnitPayloads lets you CRUD payloads for a single unit. 52 type UnitPayloads struct { 53 db Database 54 unit string 55 machine string 56 } 57 58 // List has two different modes of operation, because that's never a bad 59 // idea. If you pass no args, it returns information about all payloads 60 // tracked by the unit; if you pass names, it returns a slice of results 61 // corresponding to names, in which any names not tracked have both the 62 // NotFound field *and* an Error set. 63 func (up UnitPayloads) List(names ...string) ([]payload.Result, error) { 64 65 var sel bson.D 66 var out func([]payloadDoc) []payload.Result 67 if len(names) == 0 { 68 sel = nsPayloads.forUnit(up.unit) 69 out = nsPayloads.asResults 70 } else { 71 sel = nsPayloads.forUnitWithNames(up.unit, names) 72 out = func(docs []payloadDoc) []payload.Result { 73 return nsPayloads.orderedResults(docs, names) 74 } 75 } 76 77 coll, closer := up.db.GetCollection(payloadsC) 78 defer closer() 79 var docs []payloadDoc 80 if err := coll.Find(sel).All(&docs); err != nil { 81 return nil, errors.Trace(err) 82 } 83 return out(docs), nil 84 } 85 86 // LookUp returns its first argument and no error. 87 func (UnitPayloads) LookUp(name, rawID string) (string, error) { 88 // This method *is* used in the apiserver layer, both to extract 89 // the name from the payload and to implement the LookUp facade 90 // method which allows clients to ask the server what the first 91 // of two strings might be. 92 // 93 // The previous implementation would hit the db to as well, to 94 // exactly the same effect as implemented here. Would drop the 95 // whole useless slice, but don't want to bloat the diff. 96 return name, nil 97 } 98 99 // Track inserts the provided payload info in state. If the payload 100 // is already in the DB then it is replaced. 101 func (up UnitPayloads) Track(pl payload.Payload) error { 102 103 // XXX OMFG payload/context/register.go:83 launches bad data 104 // which flies on a majestic unvalidated arc right through the 105 // system until it lands here. This code should be: 106 // 107 // if pl.Unit != up.unit { 108 // return errors.NotValidf("unexpected Unit %q", pl.Unit) 109 // } 110 // 111 // ...but is instead: 112 pl.Unit = up.unit 113 114 if err := pl.Validate(); err != nil { 115 return errors.Trace(err) 116 } 117 118 doc := nsPayloads.asDoc(payload.FullPayloadInfo{ 119 Payload: pl, 120 Machine: up.machine, 121 }) 122 change := payloadTrackChange{doc} 123 if err := Apply(up.db, change); err != nil { 124 return errors.Trace(err) 125 } 126 return nil 127 } 128 129 // SetStatus updates the raw status for the identified payload to the 130 // provided value. If the payload is missing then payload.ErrNotFound 131 // is returned. 132 func (up UnitPayloads) SetStatus(name, status string) error { 133 if err := payload.ValidateState(status); err != nil { 134 return errors.Trace(err) 135 } 136 137 change := payloadSetStatusChange{ 138 Unit: up.unit, 139 Name: name, 140 Status: status, 141 } 142 if err := Apply(up.db, change); err != nil { 143 return errors.Trace(err) 144 } 145 return nil 146 } 147 148 // Untrack removes the identified payload from state. It does not 149 // trigger the actual destruction of the payload. If the payload is 150 // missing then this is a noop. 151 func (up UnitPayloads) Untrack(name string) error { 152 logger.Tracef("untracking %q", name) 153 change := payloadUntrackChange{ 154 Unit: up.unit, 155 Name: name, 156 } 157 if err := Apply(up.db, change); err != nil { 158 return errors.Trace(err) 159 } 160 return nil 161 } 162 163 // payloadTrackChange records a single unit payload. 164 type payloadTrackChange struct { 165 Doc payloadDoc 166 } 167 168 // Prepare is part of the Change interface. 169 func (change payloadTrackChange) Prepare(db Database) ([]txn.Op, error) { 170 171 unit := change.Doc.UnitID 172 units, closer := db.GetCollection(unitsC) 173 defer closer() 174 unitOp, err := nsLife.notDeadOp(units, unit) 175 if errors.Cause(err) == errDeadOrGone { 176 return nil, errors.Errorf("unit %q no longer available", unit) 177 } else if err != nil { 178 return nil, errors.Trace(err) 179 } 180 181 payloads, closer := db.GetCollection(payloadsC) 182 defer closer() 183 payloadOp, err := nsPayloads.trackOp(payloads, change.Doc) 184 if err != nil { 185 return nil, errors.Trace(err) 186 } 187 188 return []txn.Op{unitOp, payloadOp}, nil 189 } 190 191 // payloadSetStatusChange updates a single payload status. 192 type payloadSetStatusChange struct { 193 Unit string 194 Name string 195 Status string 196 } 197 198 // Prepare is part of the Change interface. 199 func (change payloadSetStatusChange) Prepare(db Database) ([]txn.Op, error) { 200 docID := nsPayloads.docID(change.Unit, change.Name) 201 payloads, closer := db.GetCollection(payloadsC) 202 defer closer() 203 204 op, err := nsPayloads.setStatusOp(payloads, docID, change.Status) 205 if errors.Cause(err) == errAlreadyRemoved { 206 return nil, payload.ErrNotFound 207 } else if err != nil { 208 return nil, errors.Trace(err) 209 } 210 return []txn.Op{op}, nil 211 } 212 213 // payloadUntrackChange removes a single unit payload. 214 type payloadUntrackChange struct { 215 Unit string 216 Name string 217 } 218 219 // Prepare is part of the Change interface. 220 func (change payloadUntrackChange) Prepare(db Database) ([]txn.Op, error) { 221 docID := nsPayloads.docID(change.Unit, change.Name) 222 payloads, closer := db.GetCollection(payloadsC) 223 defer closer() 224 225 op, err := nsPayloads.untrackOp(payloads, docID) 226 if errors.Cause(err) == errAlreadyRemoved { 227 return nil, ErrChangeComplete 228 } else if err != nil { 229 return nil, errors.Trace(err) 230 } 231 return []txn.Op{op}, nil 232 } 233 234 // payloadCleanupChange removes all unit payloads. 235 type payloadCleanupChange struct { 236 Unit string 237 } 238 239 // Prepare is part of the Change interface. 240 func (change payloadCleanupChange) Prepare(db Database) ([]txn.Op, error) { 241 payloads, closer := db.GetCollection(payloadsC) 242 defer closer() 243 244 sel := nsPayloads.forUnit(change.Unit) 245 fields := bson.D{{"_id", 1}} 246 var docs []struct { 247 DocID string `bson:"_id"` 248 } 249 err := payloads.Find(sel).Select(fields).All(&docs) 250 if err != nil { 251 return nil, errors.Trace(err) 252 } else if len(docs) == 0 { 253 return nil, ErrChangeComplete 254 } 255 256 ops := make([]txn.Op, 0, len(docs)) 257 for _, doc := range docs { 258 op, err := nsPayloads.untrackOp(payloads, doc.DocID) 259 if errors.Cause(err) == errAlreadyRemoved { 260 continue 261 } else if err != nil { 262 return nil, errors.Trace(err) 263 } 264 ops = append(ops, op) 265 } 266 return ops, nil 267 }