github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/payload/persistence/unit.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package persistence 5 6 // TODO(ericsnow) Eliminate the mongo-related imports here. 7 8 import ( 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 jujutxn "github.com/juju/txn" 12 "gopkg.in/mgo.v2/txn" 13 14 "github.com/juju/juju/payload" 15 ) 16 17 var logger = loggo.GetLogger("juju.payload.persistence") 18 19 // TODO(ericsnow) Merge Persistence and EnvPersistence. 20 21 // TODO(ericsnow) Store status in the status collection? 22 23 // TODO(ericsnow) Implement persistence using a TXN abstraction (used 24 // in the business logic) with ops factories available from the 25 // persistence layer. 26 27 // TODO(ericsnow) Move PersistenceBase to the components package? 28 29 // PersistenceBase exposes the core persistence functionality needed 30 // for payloads. 31 type PersistenceBase interface { 32 // One populates doc with the document corresponding to the given 33 // ID. Missing documents result in errors.NotFound. 34 One(collName, id string, doc interface{}) error 35 // All populates docs with the list of the documents corresponding 36 // to the provided query. 37 All(collName string, query, docs interface{}) error 38 // Run runs the transaction generated by the provided factory 39 // function. It may be retried several times. 40 Run(transactions jujutxn.TransactionSource) error 41 } 42 43 // Persistence exposes the high-level persistence functionality 44 // related to payloads in Juju. 45 type Persistence struct { 46 st PersistenceBase 47 unit string 48 } 49 50 // NewPersistence builds a new Persistence based on the provided info. 51 func NewPersistence(st PersistenceBase, unit string) *Persistence { 52 return &Persistence{ 53 st: st, 54 unit: unit, 55 } 56 } 57 58 // Track adds records for the payload to persistence. If the payload 59 // is already there then false gets returned (true if inserted). 60 // Existing records are not checked for consistency. 61 func (pp Persistence) Track(id string, pl payload.Payload) (bool, error) { 62 logger.Tracef("insertng %#v", pl) 63 64 _, err := pp.LookUp(pl.Name, pl.ID) 65 if err == nil { 66 return false, errors.AlreadyExistsf("payload for %q", pl.FullID()) 67 } else if !errors.IsNotFound(err) { 68 return false, errors.Annotate(err, "while checking for collisions") 69 } 70 // TODO(ericsnow) There is a *slight* race here. I haven't found 71 // a simple way to check the secondary key in the transaction. 72 73 var okay bool 74 var ops []txn.Op 75 // TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)? 76 ops = append(ops, pp.newInsertPayloadOps(id, pl)...) 77 buildTxn := func(attempt int) ([]txn.Op, error) { 78 if attempt > 0 { 79 okay = false 80 return nil, jujutxn.ErrNoOperations 81 } 82 okay = true 83 return ops, nil 84 } 85 if err := pp.st.Run(buildTxn); err != nil { 86 return false, errors.Trace(err) 87 } 88 return okay, nil 89 } 90 91 // SetStatus updates the raw status for the identified payload in 92 // persistence. The return value corresponds to whether or not the 93 // record was found in persistence. Any other problem results in 94 // an error. The payload is not checked for inconsistent records. 95 func (pp Persistence) SetStatus(id, status string) (bool, error) { 96 logger.Tracef("setting status for %q", id) 97 98 var found bool 99 var ops []txn.Op 100 // TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)? 101 ops = append(ops, pp.newSetRawStatusOps(id, status)...) 102 buildTxn := func(attempt int) ([]txn.Op, error) { 103 if attempt > 0 { 104 found = false 105 return nil, jujutxn.ErrNoOperations 106 } 107 found = true 108 return ops, nil 109 } 110 if err := pp.st.Run(buildTxn); err != nil { 111 return false, errors.Trace(err) 112 } 113 return found, nil 114 } 115 116 // List builds the list of payloads found in persistence which match 117 // the provided IDs. The lists of IDs with missing records is also 118 // returned. 119 func (pp Persistence) List(ids ...string) ([]payload.Payload, []string, error) { 120 // TODO(ericsnow) Ensure that the unit is Alive? 121 122 docs, err := pp.payloads(ids) 123 if err != nil { 124 return nil, nil, errors.Trace(err) 125 } 126 127 var results []payload.Payload 128 var missing []string 129 for _, id := range ids { 130 p, ok := pp.extractPayload(id, docs) 131 if !ok { 132 missing = append(missing, id) 133 continue 134 } 135 results = append(results, *p) 136 } 137 return results, missing, nil 138 } 139 140 // ListAll builds the list of all payloads found in persistence. 141 // Inconsistent records result in errors.NotValid. 142 func (pp Persistence) ListAll() ([]payload.Payload, error) { 143 // TODO(ericsnow) Ensure that the unit is Alive? 144 145 docs, err := pp.allPayloads() 146 if err != nil { 147 return nil, errors.Trace(err) 148 } 149 150 var results []payload.Payload 151 for id := range docs { 152 p, _ := pp.extractPayload(id, docs) 153 results = append(results, *p) 154 } 155 return results, nil 156 } 157 158 // LookUp returns the payload ID for the given name/rawID pair. 159 func (pp Persistence) LookUp(name, rawID string) (string, error) { 160 // TODO(ericsnow) This could be more efficient. 161 162 docs, err := pp.allPayloads() 163 if err != nil { 164 return "", errors.Trace(err) 165 } 166 167 for id, doc := range docs { 168 if doc.match(name, rawID) { 169 return id, nil 170 } 171 } 172 173 return "", errors.NotFoundf("payload for %s/%s", name, rawID) 174 } 175 176 // TODO(ericsnow) Add payloads to state/cleanup.go. 177 178 // TODO(ericsnow) How to ensure they are completely removed from state 179 // (when you factor in status stored in a separate collection)? 180 181 // Untrack removes all records associated with the identified payload 182 // from persistence. Also returned is whether or not the payload was 183 // found. If the records for the payload are not consistent then 184 // errors.NotValid is returned. 185 func (pp Persistence) Untrack(id string) (bool, error) { 186 var found bool 187 var ops []txn.Op 188 // TODO(ericsnow) Add unitPersistence.newEnsureAliveOp(pp.unit)? 189 ops = append(ops, pp.newRemovePayloadOps(id)...) 190 buildTxn := func(attempt int) ([]txn.Op, error) { 191 if attempt > 0 { 192 found = false 193 return nil, jujutxn.ErrNoOperations 194 } 195 found = true 196 return ops, nil 197 } 198 if err := pp.st.Run(buildTxn); err != nil { 199 return false, errors.Trace(err) 200 } 201 return found, nil 202 }