github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/endpoint_bindings.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 jujutxn "github.com/juju/txn" 9 "github.com/juju/utils/set" 10 "gopkg.in/juju/charm.v6-unstable" 11 "gopkg.in/mgo.v2" 12 "gopkg.in/mgo.v2/bson" 13 "gopkg.in/mgo.v2/txn" 14 ) 15 16 // endpointBindingsDoc represents how a service endpoints are bound to spaces. 17 // The DocID field contains the service's global key, so there is always one 18 // endpointBindingsDoc per service. 19 type endpointBindingsDoc struct { 20 // DocID is always the same as a service's global key. 21 DocID string `bson:"_id"` 22 23 // Bindings maps a service endpoint name to the space name it is bound to. 24 Bindings bindingsMap `bson:"bindings"` 25 26 // TxnRevno is used to assert the collection have not changed since this 27 // document was fetched. 28 TxnRevno int64 `bson:"txn-revno"` 29 } 30 31 // bindingsMap is the underlying type stored in mongo for bindings. 32 type bindingsMap map[string]string 33 34 // SetBSON ensures any special characters ($ or .) are unescaped in keys after 35 // unmarshalling the raw BSON coming from the stored document. 36 func (bp *bindingsMap) SetBSON(raw bson.Raw) error { 37 rawMap := make(map[string]string) 38 if err := raw.Unmarshal(rawMap); err != nil { 39 return err 40 } 41 for key, value := range rawMap { 42 newKey := unescapeReplacer.Replace(key) 43 if newKey != key { 44 delete(rawMap, key) 45 } 46 rawMap[newKey] = value 47 } 48 *bp = bindingsMap(rawMap) 49 return nil 50 } 51 52 // GetBSON ensures any special characters ($ or .) are escaped in keys before 53 // marshalling the map into BSON and storing in mongo. 54 func (b bindingsMap) GetBSON() (interface{}, error) { 55 if b == nil || len(b) == 0 { 56 // We need to return a non-nil map otherwise bson.Unmarshal 57 // call will fail when reading the doc back. 58 return make(map[string]string), nil 59 } 60 rawMap := make(map[string]string, len(b)) 61 for key, value := range b { 62 newKey := escapeReplacer.Replace(key) 63 rawMap[newKey] = value 64 } 65 return rawMap, nil 66 } 67 68 // mergeBindings returns the effective bindings, by combining the default 69 // bindings based on the given charm metadata, overriding them first with 70 // matching oldMap values, and then with newMap values (for the same keys). 71 // newMap and oldMap are both optional and will ignored when empty. Returns a 72 // map containing only those bindings that need updating, and a sorted slice of 73 // keys to remove (if any) - those are present in oldMap but missing in both 74 // newMap and defaults. 75 func mergeBindings(newMap, oldMap map[string]string, meta *charm.Meta) (map[string]string, []string, error) { 76 defaultsMap := DefaultEndpointBindingsForCharm(meta) 77 78 // defaultsMap contains all endpoints that must be bound for the given charm 79 // metadata, but we need to figure out which value to use for each key. 80 updated := make(map[string]string) 81 for key, defaultValue := range defaultsMap { 82 effectiveValue := defaultValue 83 84 oldValue, hasOld := oldMap[key] 85 if hasOld && oldValue != effectiveValue { 86 effectiveValue = oldValue 87 } 88 89 newValue, hasNew := newMap[key] 90 if hasNew && newValue != effectiveValue { 91 effectiveValue = newValue 92 } 93 94 updated[key] = effectiveValue 95 } 96 97 // Any other bindings in newMap are most likely extraneous, but add them 98 // anyway and let the validation handle them. 99 for key, newValue := range newMap { 100 if _, defaultExists := defaultsMap[key]; !defaultExists { 101 updated[key] = newValue 102 } 103 } 104 105 // All defaults were processed, so anything else in oldMap not about to be 106 // updated and not having a default for the given metadata needs to be 107 // removed. 108 removedKeys := set.NewStrings() 109 for key := range oldMap { 110 if _, updating := updated[key]; !updating { 111 removedKeys.Add(key) 112 } 113 if _, defaultExists := defaultsMap[key]; !defaultExists { 114 removedKeys.Add(key) 115 } 116 } 117 removed := removedKeys.SortedValues() 118 return updated, removed, nil 119 } 120 121 // createEndpointBindingsOp returns the op needed to create new endpoint 122 // bindings using the optional givenMap and the specified charm metadata to for 123 // determining defaults and to validate the effective bindings. 124 func createEndpointBindingsOp(st *State, key string, givenMap map[string]string, meta *charm.Meta) (txn.Op, error) { 125 126 // No existing map to merge, just use the defaults. 127 initialMap, _, err := mergeBindings(givenMap, nil, meta) 128 if err != nil { 129 return txn.Op{}, errors.Trace(err) 130 } 131 132 // Validate the bindings before inserting. 133 if err := validateEndpointBindingsForCharm(st, initialMap, meta); err != nil { 134 return txn.Op{}, errors.Trace(err) 135 } 136 137 return txn.Op{ 138 C: endpointBindingsC, 139 Id: key, 140 Assert: txn.DocMissing, 141 Insert: endpointBindingsDoc{ 142 Bindings: initialMap, 143 }, 144 }, nil 145 } 146 147 // updateEndpointBindingsOp returns an op that merges the existing bindings with 148 // givenMap, using newMeta to validate the merged bindings, and asserting the 149 // existing ones haven't changed in the since we fetched them. 150 func updateEndpointBindingsOp(st *State, key string, givenMap map[string]string, newMeta *charm.Meta) (txn.Op, error) { 151 // Fetch existing bindings. 152 existingMap, txnRevno, err := readEndpointBindings(st, key) 153 if err != nil && !errors.IsNotFound(err) { 154 return txn.Op{}, errors.Trace(err) 155 } 156 157 // Merge existing with given as needed. 158 updatedMap, removedKeys, err := mergeBindings(givenMap, existingMap, newMeta) 159 if err != nil { 160 return txn.Op{}, errors.Trace(err) 161 } 162 163 // Validate the bindings before updating. 164 if err := validateEndpointBindingsForCharm(st, updatedMap, newMeta); err != nil { 165 return txn.Op{}, errors.Trace(err) 166 } 167 168 // Prepare the update operations. 169 sanitize := inSubdocEscapeReplacer("bindings") 170 changes := make(bson.M, len(updatedMap)) 171 for endpoint, space := range updatedMap { 172 changes[sanitize(endpoint)] = space 173 } 174 deletes := make(bson.M, len(removedKeys)) 175 for _, endpoint := range removedKeys { 176 deletes[sanitize(endpoint)] = 1 177 } 178 179 var update bson.D 180 if len(changes) != 0 { 181 update = append(update, bson.DocElem{Name: "$set", Value: changes}) 182 } 183 if len(deletes) != 0 { 184 update = append(update, bson.DocElem{Name: "$unset", Value: deletes}) 185 } 186 if len(update) == 0 { 187 return txn.Op{}, jujutxn.ErrNoOperations 188 } 189 updateOp := txn.Op{ 190 C: endpointBindingsC, 191 Id: key, 192 Update: update, 193 } 194 if existingMap != nil { 195 // Only assert existing haven't changed when they actually exist. 196 updateOp.Assert = bson.D{{"txn-revno", txnRevno}} 197 } 198 return updateOp, nil 199 } 200 201 // removeEndpointBindingsOp returns an op removing the bindings for the given 202 // key, without asserting they exist in the first place. 203 func removeEndpointBindingsOp(key string) txn.Op { 204 return txn.Op{ 205 C: endpointBindingsC, 206 Id: key, 207 Remove: true, 208 } 209 } 210 211 // readEndpointBindings returns the stored bindings and TxnRevno for the given 212 // service global key, or an error satisfying errors.IsNotFound() otherwise. 213 func readEndpointBindings(st *State, key string) (map[string]string, int64, error) { 214 endpointBindings, closer := st.getCollection(endpointBindingsC) 215 defer closer() 216 217 var doc endpointBindingsDoc 218 err := endpointBindings.FindId(key).One(&doc) 219 if err == mgo.ErrNotFound { 220 return nil, 0, errors.NotFoundf("endpoint bindings for %q", key) 221 } 222 if err != nil { 223 return nil, 0, errors.Annotatef(err, "cannot get endpoint bindings for %q", key) 224 } 225 226 return doc.Bindings, doc.TxnRevno, nil 227 } 228 229 // validateEndpointBindingsForCharm verifies that all endpoint names in bindings 230 // are valid for the given charm metadata, and each endpoint is bound to a known 231 // space - otherwise an error satisfying errors.IsNotValid() will be returned. 232 func validateEndpointBindingsForCharm(st *State, bindings map[string]string, charmMeta *charm.Meta) error { 233 if st == nil { 234 return errors.NotValidf("nil state") 235 } 236 if bindings == nil { 237 return errors.NotValidf("nil bindings") 238 } 239 if charmMeta == nil { 240 return errors.NotValidf("nil charm metadata") 241 } 242 spaces, err := st.AllSpaces() 243 if err != nil { 244 return errors.Trace(err) 245 } 246 247 spacesNamesSet := set.NewStrings() 248 for _, space := range spaces { 249 spacesNamesSet.Add(space.Name()) 250 } 251 252 allBindings := DefaultEndpointBindingsForCharm(charmMeta) 253 endpointsNamesSet := set.NewStrings() 254 for name := range allBindings { 255 endpointsNamesSet.Add(name) 256 } 257 258 // Ensure there are no unknown endpoints and/or spaces specified. 259 // 260 // TODO(dimitern): This assumes spaces cannot be deleted when they are used 261 // in bindings. In follow-up, this will be enforced by using refcounts on 262 // spaces. 263 for endpoint, space := range bindings { 264 if !endpointsNamesSet.Contains(endpoint) { 265 return errors.NotValidf("unknown endpoint %q", endpoint) 266 } 267 if space != "" && !spacesNamesSet.Contains(space) { 268 return errors.NotValidf("unknown space %q", space) 269 } 270 } 271 return nil 272 } 273 274 // DefaultEndpointBindingsForCharm populates a bindings map containing each 275 // endpoint of the given charm metadata (relation name or extra-binding name) 276 // bound to an empty space. 277 func DefaultEndpointBindingsForCharm(charmMeta *charm.Meta) map[string]string { 278 allRelations := charmMeta.CombinedRelations() 279 bindings := make(map[string]string, len(allRelations)+len(charmMeta.ExtraBindings)) 280 for name := range allRelations { 281 bindings[name] = "" 282 } 283 for name := range charmMeta.ExtraBindings { 284 bindings[name] = "" 285 } 286 return bindings 287 }