launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/state/settings.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "sort" 9 "strings" 10 11 "labix.org/v2/mgo" 12 "labix.org/v2/mgo/txn" 13 14 errgo "launchpad.net/errgo/errors" 15 "launchpad.net/juju-core/errors" 16 ) 17 18 // See: http://docs.mongodb.org/manual/faq/developers/#faq-dollar-sign-escaping 19 // for why we're using those replacements. 20 const ( 21 fullWidthDot = "\uff0e" 22 fullWidthDollar = "\uff04" 23 ) 24 25 var ( 26 escapeReplacer = strings.NewReplacer(".", fullWidthDot, "$", fullWidthDollar) 27 unescapeReplacer = strings.NewReplacer(fullWidthDot, ".", fullWidthDollar, "$") 28 ) 29 30 const ( 31 ItemAdded = iota 32 ItemModified 33 ItemDeleted 34 ) 35 36 // ItemChange represents the change of an item in a settings. 37 type ItemChange struct { 38 Type int 39 Key string 40 OldValue interface{} 41 NewValue interface{} 42 } 43 44 // String returns the item change in a readable format. 45 func (ic *ItemChange) String() string { 46 switch ic.Type { 47 case ItemAdded: 48 return fmt.Sprintf("setting added: %v = %v", ic.Key, ic.NewValue) 49 case ItemModified: 50 return fmt.Sprintf("setting modified: %v = %v (was %v)", 51 ic.Key, ic.NewValue, ic.OldValue) 52 case ItemDeleted: 53 return fmt.Sprintf("setting deleted: %v (was %v)", ic.Key, ic.OldValue) 54 } 55 return fmt.Sprintf("unknown setting change type %d: %v = %v (was %v)", 56 ic.Type, ic.Key, ic.NewValue, ic.OldValue) 57 } 58 59 // itemChangeSlice contains a slice of item changes in a config node. 60 // It implements the sort interface to sort the items changes by key. 61 type itemChangeSlice []ItemChange 62 63 func (ics itemChangeSlice) Len() int { return len(ics) } 64 func (ics itemChangeSlice) Less(i, j int) bool { return ics[i].Key < ics[j].Key } 65 func (ics itemChangeSlice) Swap(i, j int) { ics[i], ics[j] = ics[j], ics[i] } 66 67 // A Settings manages changes to settings as a delta in memory and merges 68 // them back in the database when explicitly requested. 69 type Settings struct { 70 st *State 71 key string 72 // disk holds the values in the config node before 73 // any keys have been changed. It is reset on Read and Write 74 // operations. 75 disk map[string]interface{} 76 // cache holds the current values in the config node. 77 // The difference between disk and core 78 // determines the delta to be applied when Settings.Write 79 // is called. 80 core map[string]interface{} 81 txnRevno int64 82 } 83 84 // Keys returns the current keys in alphabetical order. 85 func (c *Settings) Keys() []string { 86 keys := []string{} 87 for key := range c.core { 88 keys = append(keys, key) 89 } 90 sort.Strings(keys) 91 return keys 92 } 93 94 // Get returns the value of key and whether it was found. 95 func (c *Settings) Get(key string) (value interface{}, found bool) { 96 value, found = c.core[key] 97 return 98 } 99 100 // Map returns all keys and values of the node. 101 func (c *Settings) Map() map[string]interface{} { 102 return copyMap(c.core, nil) 103 } 104 105 // Set sets key to value 106 func (c *Settings) Set(key string, value interface{}) { 107 c.core[key] = value 108 } 109 110 // Update sets multiple key/value pairs. 111 func (c *Settings) Update(kv map[string]interface{}) { 112 for key, value := range kv { 113 c.core[key] = value 114 } 115 } 116 117 // Delete removes key. 118 func (c *Settings) Delete(key string) { 119 delete(c.core, key) 120 } 121 122 // cacheKeys returns the keys of all caches as a key=>true map. 123 func cacheKeys(caches ...map[string]interface{}) map[string]bool { 124 keys := make(map[string]bool) 125 for _, cache := range caches { 126 for key := range cache { 127 keys[key] = true 128 } 129 } 130 return keys 131 } 132 133 // Write writes changes made to c back onto its node. Changes are written 134 // as a delta applied on top of the latest version of the node, to prevent 135 // overwriting unrelated changes made to the node since it was last read. 136 func (c *Settings) Write() ([]ItemChange, error) { 137 changes := []ItemChange{} 138 updates := map[string]interface{}{} 139 deletions := map[string]int{} 140 for key := range cacheKeys(c.disk, c.core) { 141 old, ondisk := c.disk[key] 142 new, incore := c.core[key] 143 if new == old { 144 continue 145 } 146 var change ItemChange 147 escapedKey := escapeReplacer.Replace(key) 148 switch { 149 case incore && ondisk: 150 change = ItemChange{ItemModified, key, old, new} 151 updates[escapedKey] = new 152 case incore && !ondisk: 153 change = ItemChange{ItemAdded, key, nil, new} 154 updates[escapedKey] = new 155 case ondisk && !incore: 156 change = ItemChange{ItemDeleted, key, old, nil} 157 deletions[escapedKey] = 1 158 default: 159 panic("unreachable") 160 } 161 changes = append(changes, change) 162 } 163 if len(changes) == 0 { 164 return []ItemChange{}, nil 165 } 166 sort.Sort(itemChangeSlice(changes)) 167 ops := []txn.Op{{ 168 C: c.st.settings.Name, 169 Id: c.key, 170 Assert: txn.DocExists, 171 Update: D{ 172 {"$set", updates}, 173 {"$unset", deletions}, 174 }, 175 }} 176 err := c.st.runTransaction(ops) 177 if errgo.Cause(err) == txn.ErrAborted { 178 return nil, errors.NotFoundf("settings") 179 } 180 if err != nil { 181 return nil, errgo.Notef(err, "cannot write settings") 182 } 183 c.disk = copyMap(c.core, nil) 184 return changes, nil 185 } 186 187 func newSettings(st *State, key string) *Settings { 188 return &Settings{ 189 st: st, 190 key: key, 191 core: make(map[string]interface{}), 192 } 193 } 194 195 // cleanSettingsMap cleans the map of version and _id fields and also unescapes 196 // keys coming out of MongoDB. 197 func cleanSettingsMap(in map[string]interface{}) { 198 delete(in, "_id") 199 delete(in, "txn-revno") 200 delete(in, "txn-queue") 201 replaceKeys(in, unescapeReplacer.Replace) 202 } 203 204 // replaceKeys will modify the provided map in place by replacing keys with 205 // their replacement if they have been modified. 206 func replaceKeys(m map[string]interface{}, replace func(string) string) { 207 for key, value := range m { 208 if newKey := replace(key); newKey != key { 209 delete(m, key) 210 m[newKey] = value 211 } 212 } 213 return 214 } 215 216 // copyMap copies the keys and values of one map into a new one. If replace 217 // is non-nil, for each old key k, the new key will be replace(k). 218 func copyMap(in map[string]interface{}, replace func(string) string) (out map[string]interface{}) { 219 out = make(map[string]interface{}) 220 for key, value := range in { 221 if replace != nil { 222 key = replace(key) 223 } 224 out[key] = value 225 } 226 return 227 } 228 229 // Read (re)reads the node data into c. 230 func (c *Settings) Read() error { 231 config, txnRevno, err := readSettingsDoc(c.st, c.key) 232 if errgo.Cause(err) == mgo.ErrNotFound { 233 c.disk = nil 234 c.core = make(map[string]interface{}) 235 return errors.NotFoundf("settings") 236 } 237 if err != nil { 238 return errgo.Notef(err, "cannot read settings") 239 } 240 c.txnRevno = txnRevno 241 c.disk = config 242 c.core = copyMap(config, nil) 243 return nil 244 } 245 246 // readSettingsDoc reads the settings with the given 247 // key. It returns the settings and the current rxnRevno. 248 func readSettingsDoc(st *State, key string) (map[string]interface{}, int64, error) { 249 config := map[string]interface{}{} 250 err := st.settings.FindId(key).One(config) 251 if err != nil { 252 return nil, 0, mask(err, errgo.Is(mgo.ErrNotFound)) 253 } 254 txnRevno := config["txn-revno"].(int64) 255 cleanSettingsMap(config) 256 return config, txnRevno, nil 257 } 258 259 // readSettings returns the Settings for key. 260 func readSettings(st *State, key string) (*Settings, error) { 261 s := newSettings(st, key) 262 if err := s.Read(); err != nil { 263 return nil, mask(err, errors.IsNotFoundError) 264 } 265 return s, nil 266 } 267 268 var errSettingsExist = errgo.Newf("cannot overwrite existing settings") 269 270 func createSettingsOp(st *State, key string, values map[string]interface{}) txn.Op { 271 newValues := copyMap(values, escapeReplacer.Replace) 272 return txn.Op{ 273 C: st.settings.Name, 274 Id: key, 275 Assert: txn.DocMissing, 276 Insert: newValues, 277 } 278 } 279 280 // createSettings writes an initial config node. 281 func createSettings(st *State, key string, values map[string]interface{}) (*Settings, error) { 282 s := newSettings(st, key) 283 s.core = copyMap(values, nil) 284 ops := []txn.Op{createSettingsOp(st, key, values)} 285 err := s.st.runTransaction(ops) 286 if errgo.Cause(err) == txn.ErrAborted { 287 return nil, errSettingsExist 288 } 289 if err != nil { 290 return nil, errgo.Notef(err, "cannot create settings") 291 } 292 return s, nil 293 } 294 295 // removeSettings removes the Settings for key. 296 func removeSettings(st *State, key string) error { 297 err := st.settings.RemoveId(key) 298 if errgo.Cause(err) == mgo.ErrNotFound { 299 return errors.NotFoundf("settings") 300 } 301 return nil 302 } 303 304 // replaceSettingsOp returns a txn.Op that deletes the document's contents and 305 // replaces it with the supplied values, and a function that should be called on 306 // txn failure to determine whether this operation failed (due to a concurrent 307 // settings change). 308 func replaceSettingsOp(st *State, key string, values map[string]interface{}) (txn.Op, func() (bool, error), error) { 309 s, err := readSettings(st, key) 310 if err != nil { 311 return txn.Op{}, nil, mask(err) 312 } 313 deletes := map[string]int{} 314 for k := range s.disk { 315 if _, found := values[k]; !found { 316 deletes[escapeReplacer.Replace(k)] = 1 317 } 318 } 319 newValues := copyMap(values, escapeReplacer.Replace) 320 op := s.assertUnchangedOp() 321 op.Update = D{ 322 {"$set", newValues}, 323 {"$unset", deletes}, 324 } 325 assertFailed := func() (bool, error) { 326 latest, err := readSettings(st, key) 327 if err != nil { 328 return false, mask(err) 329 } 330 return latest.txnRevno != s.txnRevno, nil 331 } 332 return op, assertFailed, nil 333 } 334 335 func (s *Settings) assertUnchangedOp() txn.Op { 336 return txn.Op{ 337 C: s.st.settings.Name, 338 Id: s.key, 339 Assert: D{{"txn-revno", s.txnRevno}}, 340 } 341 }