github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "github.com/juju/mgo/v3/bson" 9 "github.com/juju/mgo/v3/txn" 10 11 "github.com/juju/juju/core/payloads" 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() ([]payloads.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 unit.ShouldBeAssigned() && 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) ([]payloads.Result, error) { 64 65 var sel bson.D 66 var out func([]payloadDoc) []payloads.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) []payloads.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 payloads.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(payloads.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 payloads.ErrNotFound 131 // is returned. 132 func (up UnitPayloads) SetStatus(name, status string) error { 133 if err := payloads.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 unit := change.Doc.UnitID 171 units, uCloser := db.GetCollection(unitsC) 172 defer uCloser() 173 unitOp, err := nsLife.notDeadOp(units, unit) 174 if errors.Cause(err) == errDeadOrGone { 175 return nil, errors.Errorf("unit %q no longer available", unit) 176 } else if err != nil { 177 return nil, errors.Trace(err) 178 } 179 180 payloads, pCloser := db.GetCollection(payloadsC) 181 defer pCloser() 182 payloadOp, err := nsPayloads.trackOp(payloads, change.Doc) 183 if err != nil { 184 return nil, errors.Trace(err) 185 } 186 187 return []txn.Op{unitOp, payloadOp}, nil 188 } 189 190 // payloadSetStatusChange updates a single payload status. 191 type payloadSetStatusChange struct { 192 Unit string 193 Name string 194 Status string 195 } 196 197 // Prepare is part of the Change interface. 198 func (change payloadSetStatusChange) Prepare(db Database) ([]txn.Op, error) { 199 docID := nsPayloads.docID(change.Unit, change.Name) 200 payloadColl, closer := db.GetCollection(payloadsC) 201 defer closer() 202 203 op, err := nsPayloads.setStatusOp(payloadColl, docID, change.Status) 204 if errors.Cause(err) == errAlreadyRemoved { 205 return nil, payloads.ErrNotFound 206 } else if err != nil { 207 return nil, errors.Trace(err) 208 } 209 return []txn.Op{op}, nil 210 } 211 212 // payloadUntrackChange removes a single unit payload. 213 type payloadUntrackChange struct { 214 Unit string 215 Name string 216 } 217 218 // Prepare is part of the Change interface. 219 func (change payloadUntrackChange) Prepare(db Database) ([]txn.Op, error) { 220 docID := nsPayloads.docID(change.Unit, change.Name) 221 payloads, closer := db.GetCollection(payloadsC) 222 defer closer() 223 224 op, err := nsPayloads.untrackOp(payloads, docID) 225 if errors.Cause(err) == errAlreadyRemoved { 226 return nil, ErrChangeComplete 227 } else if err != nil { 228 return nil, errors.Trace(err) 229 } 230 return []txn.Op{op}, nil 231 } 232 233 // payloadCleanupChange removes all unit payloads. 234 type payloadCleanupChange struct { 235 Unit string 236 } 237 238 // Prepare is part of the Change interface. 239 func (change payloadCleanupChange) Prepare(db Database) ([]txn.Op, error) { 240 payloads, closer := db.GetCollection(payloadsC) 241 defer closer() 242 243 sel := nsPayloads.forUnit(change.Unit) 244 fields := bson.D{{"_id", 1}} 245 var docs []struct { 246 DocID string `bson:"_id"` 247 } 248 err := payloads.Find(sel).Select(fields).All(&docs) 249 if err != nil { 250 return nil, errors.Trace(err) 251 } else if len(docs) == 0 { 252 return nil, ErrChangeComplete 253 } 254 255 ops := make([]txn.Op, 0, len(docs)) 256 for _, doc := range docs { 257 op, err := nsPayloads.untrackOp(payloads, doc.DocID) 258 if errors.Cause(err) == errAlreadyRemoved { 259 continue 260 } else if err != nil { 261 return nil, errors.Trace(err) 262 } 263 ops = append(ops, op) 264 } 265 return ops, nil 266 }