github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/payloads_ns.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/charm.v6-unstable" 11 "gopkg.in/mgo.v2/bson" 12 "gopkg.in/mgo.v2/txn" 13 14 "github.com/juju/juju/mongo" 15 "github.com/juju/juju/payload" 16 ) 17 18 // payloadDoc is the top-level document for payloads. 19 type payloadDoc struct { 20 // _id encodes UnitID and Name (which should theoretically 21 // match the name of a payload-class defined in the charm -- 22 // for example "my-payload" -- but nothing really checks). 23 UnitID string `bson:"unitid"` 24 Name string `bson:"name"` 25 26 // MachineID doesn't belong here. 27 MachineID string `bson:"machine-id"` 28 29 // Type is again a freeform field that might match that of a 30 // payload-class defined in the charm -- for example, "docker". 31 Type string `bson:"type"` 32 33 // RawID records the substrate-specific payload id -- for 34 // example, "9cd6338abdf09beb", the actual docker container 35 // we're tracking. 36 RawID string `bson:"rawid"` 37 38 // State is sort of like status, valid values are defined in 39 // package payloads. 40 State string `bson:"state"` 41 42 // Labels contain whatever additional arbitrary strings were 43 // left over after we hoovered up <Type> <Name> <RawID> from the 44 // command line. 45 Labels []string `bson:"labels"` 46 } 47 48 // nsPayloads_ backs nsPayloads. 49 type nsPayloads_ struct{} 50 51 // nsPayloads namespaces low-level unit-payload functionality: it's 52 // meant to be the one place in the code where we wrangle queries, 53 // serialization, and updates to payload data. (The UnitPayloads and 54 // ModelPayloads types may run queries directly, because it's silly 55 // to build *another* mongo-aping layer with its own idiosyncratic 56 // implementations of One and All and so on; but they should be getting 57 // all their queries from here, and using these methods to convert 58 // types, and generally making a point of *not* knowing anything about 59 // how the actual data is represented.) 60 var nsPayloads = nsPayloads_{} 61 62 // docID is globalKey as written by someone who thought it would be 63 // helpful to reinvent the 'u#<unit>#' prefix (which *would* indicate 64 // that payloads are things-that-exist-per-unit, and do so in a way 65 // consistent with the rest of the DB. /sigh.) 66 func (nsPayloads_) docID(unit, name string) string { 67 return fmt.Sprintf("payload#%s#%s", unit, name) 68 } 69 70 // forUnit returns a selector that matches all payloads for the unit. 71 func (nsPayloads_) forUnit(unit string) bson.D { 72 return bson.D{{"unitid", unit}} 73 } 74 75 // forUnitWithNames returns a selector that matches any payloads for the 76 // supplied unit that have the supplied names. 77 func (nsPayloads_) forUnitWithNames(unit string, names []string) bson.D { 78 ids := make([]string, 0, len(names)) 79 for _, name := range names { 80 ids = append(ids, nsPayloads.docID(unit, name)) 81 } 82 return bson.D{{"_id", bson.D{{"$in", ids}}}} 83 } 84 85 // asDoc converts a FullPayloadInfo into an independent payloadDoc. 86 func (nsPayloads_) asDoc(p payload.FullPayloadInfo) payloadDoc { 87 labels := make([]string, len(p.Labels)) 88 copy(labels, p.Labels) 89 return payloadDoc{ 90 UnitID: p.Unit, 91 Name: p.PayloadClass.Name, 92 MachineID: p.Machine, 93 Type: p.PayloadClass.Type, 94 RawID: p.ID, 95 State: p.Status, 96 Labels: labels, 97 } 98 } 99 100 // asPayload converts a payloadDoc into an independent FullPayloadInfo. 101 func (nsPayloads_) asPayload(doc payloadDoc) payload.FullPayloadInfo { 102 labels := make([]string, len(doc.Labels)) 103 copy(labels, doc.Labels) 104 p := payload.FullPayloadInfo{ 105 Payload: payload.Payload{ 106 PayloadClass: charm.PayloadClass{ 107 Name: doc.Name, 108 Type: doc.Type, 109 }, 110 ID: doc.RawID, 111 Status: doc.State, 112 Labels: labels, 113 Unit: doc.UnitID, 114 }, 115 Machine: doc.MachineID, 116 } 117 return p 118 } 119 120 // asPayloads converts a slice of payloadDocs into a corresponding slice 121 // of independent FullPayloadInfos. 122 func (nsPayloads_) asPayloads(docs []payloadDoc) []payload.FullPayloadInfo { 123 payloads := make([]payload.FullPayloadInfo, 0, len(docs)) 124 for _, doc := range docs { 125 payloads = append(payloads, nsPayloads.asPayload(doc)) 126 } 127 return payloads 128 } 129 130 // asResults converts a slice of payloadDocs into a corresponding slice 131 // of independent payload.Results. 132 func (nsPayloads_) asResults(docs []payloadDoc) []payload.Result { 133 results := make([]payload.Result, 0, len(docs)) 134 for _, doc := range docs { 135 full := nsPayloads.asPayload(doc) 136 results = append(results, payload.Result{ 137 ID: doc.Name, 138 Payload: &full, 139 }) 140 } 141 return results 142 } 143 144 // orderedResults converts payloadDocs into payload.Results, in the 145 // order defined by names, and represents missing names in the highly 146 // baroque fashion apparently designed into Results. 147 func (nsPayloads_) orderedResults(docs []payloadDoc, names []string) []payload.Result { 148 found := make(map[string]payloadDoc) 149 for _, doc := range docs { 150 found[doc.Name] = doc 151 } 152 results := make([]payload.Result, len(names)) 153 for i, name := range names { 154 results[i].ID = name 155 if doc, ok := found[name]; ok { 156 full := nsPayloads.asPayload(doc) 157 results[i].Payload = &full 158 } else { 159 results[i].NotFound = true 160 results[i].Error = errors.NotFoundf(name) 161 } 162 } 163 return results 164 } 165 166 // trackOp returns a txn.Op that will either insert or update the 167 // supplied payload, and fail if the observed precondition changes. 168 func (nsPayloads_) trackOp(payloads mongo.Collection, doc payloadDoc) (txn.Op, error) { 169 docID := nsPayloads.docID(doc.UnitID, doc.Name) 170 payloadOp := txn.Op{ 171 C: payloads.Name(), 172 Id: docID, 173 } 174 count, err := payloads.FindId(docID).Count() 175 if err != nil { 176 return txn.Op{}, errors.Trace(err) 177 } else if count == 0 { 178 payloadOp.Assert = txn.DocMissing 179 payloadOp.Insert = doc 180 } else { 181 payloadOp.Assert = txn.DocExists 182 payloadOp.Update = bson.D{{"$set", doc}} 183 } 184 return payloadOp, nil 185 } 186 187 // untrackOp returns a txn.Op that will unconditionally remove the 188 // identified document. If the payload doesn't exist, it returns 189 // errAlreadyRemoved. 190 func (nsPayloads_) untrackOp(payloads mongo.Collection, docID string) (txn.Op, error) { 191 count, err := payloads.FindId(docID).Count() 192 if err != nil { 193 return txn.Op{}, errors.Trace(err) 194 } else if count == 0 { 195 return txn.Op{}, errAlreadyRemoved 196 } 197 return txn.Op{ 198 C: payloads.Name(), 199 Id: docID, 200 Assert: txn.DocExists, 201 Remove: true, 202 }, nil 203 } 204 205 // setStatusOp returns a txn.Op that updates the status of the 206 // identified payload. If the payload doesn't exist, it returns 207 // errAlreadyRemoved. 208 func (nsPayloads_) setStatusOp(payloads mongo.Collection, docID string, status string) (txn.Op, error) { 209 count, err := payloads.FindId(docID).Count() 210 if err != nil { 211 return txn.Op{}, errors.Trace(err) 212 } else if count == 0 { 213 return txn.Op{}, errAlreadyRemoved 214 } 215 return txn.Op{ 216 C: payloads.Name(), 217 Id: docID, 218 Assert: txn.DocExists, 219 Update: bson.D{{"$set", bson.D{{"state", status}}}}, 220 }, nil 221 }