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