github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/settings_test.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 "github.com/juju/errors" 8 jc "github.com/juju/testing/checkers" 9 gc "gopkg.in/check.v1" 10 "gopkg.in/mgo.v2/txn" 11 ) 12 13 type SettingsSuite struct { 14 internalStateSuite 15 key string 16 collection string 17 } 18 19 var _ = gc.Suite(&SettingsSuite{}) 20 21 func (s *SettingsSuite) SetUpTest(c *gc.C) { 22 s.internalStateSuite.SetUpTest(c) 23 s.key = "config" 24 s.collection = settingsC 25 } 26 27 func (s *SettingsSuite) createSettings(key string, values map[string]interface{}) (*Settings, error) { 28 return createSettings(s.state, s.collection, key, values) 29 } 30 31 func (s *SettingsSuite) readSettings() (*Settings, error) { 32 return readSettings(s.state, s.collection, s.key) 33 } 34 35 func (s *SettingsSuite) TestCreateEmptySettings(c *gc.C) { 36 node, err := s.createSettings(s.key, nil) 37 c.Assert(err, jc.ErrorIsNil) 38 c.Assert(node.Keys(), gc.DeepEquals, []string{}) 39 } 40 41 func (s *SettingsSuite) TestCannotOverwrite(c *gc.C) { 42 _, err := s.createSettings(s.key, nil) 43 c.Assert(err, jc.ErrorIsNil) 44 _, err = s.createSettings(s.key, nil) 45 c.Assert(err, gc.ErrorMatches, "cannot overwrite existing settings") 46 } 47 48 func (s *SettingsSuite) TestCannotReadMissing(c *gc.C) { 49 _, err := s.readSettings() 50 c.Assert(err, gc.ErrorMatches, "settings not found") 51 c.Assert(err, jc.Satisfies, errors.IsNotFound) 52 } 53 54 func (s *SettingsSuite) TestCannotWriteMissing(c *gc.C) { 55 node, err := s.createSettings(s.key, nil) 56 c.Assert(err, jc.ErrorIsNil) 57 58 err = removeSettings(s.state, s.collection, s.key) 59 c.Assert(err, jc.ErrorIsNil) 60 61 node.Set("foo", "bar") 62 _, err = node.Write() 63 c.Assert(err, gc.ErrorMatches, "settings not found") 64 c.Assert(err, jc.Satisfies, errors.IsNotFound) 65 } 66 67 func (s *SettingsSuite) TestUpdateWithWrite(c *gc.C) { 68 node, err := s.createSettings(s.key, nil) 69 c.Assert(err, jc.ErrorIsNil) 70 options := map[string]interface{}{"alpha": "beta", "one": 1} 71 node.Update(options) 72 changes, err := node.Write() 73 c.Assert(err, jc.ErrorIsNil) 74 c.Assert(changes, gc.DeepEquals, []ItemChange{ 75 {ItemAdded, "alpha", nil, "beta"}, 76 {ItemAdded, "one", nil, 1}, 77 }) 78 79 // Check local state. 80 c.Assert(node.Map(), gc.DeepEquals, options) 81 82 // Check MongoDB state. 83 var mgoData struct { 84 Settings settingsMap 85 } 86 settings, closer := s.state.getCollection(settingsC) 87 defer closer() 88 err = settings.FindId(s.key).One(&mgoData) 89 c.Assert(err, jc.ErrorIsNil) 90 c.Assert(map[string]interface{}(mgoData.Settings), gc.DeepEquals, options) 91 } 92 93 func (s *SettingsSuite) TestConflictOnSet(c *gc.C) { 94 // Check version conflict errors. 95 nodeOne, err := s.createSettings(s.key, nil) 96 c.Assert(err, jc.ErrorIsNil) 97 nodeTwo, err := s.readSettings() 98 c.Assert(err, jc.ErrorIsNil) 99 100 optionsOld := map[string]interface{}{"alpha": "beta", "one": 1} 101 nodeOne.Update(optionsOld) 102 nodeOne.Write() 103 104 nodeTwo.Update(optionsOld) 105 changes, err := nodeTwo.Write() 106 c.Assert(err, jc.ErrorIsNil) 107 c.Assert(changes, gc.DeepEquals, []ItemChange{ 108 {ItemAdded, "alpha", nil, "beta"}, 109 {ItemAdded, "one", nil, 1}, 110 }) 111 112 // First test node one. 113 c.Assert(nodeOne.Map(), gc.DeepEquals, optionsOld) 114 115 // Write on node one. 116 optionsNew := map[string]interface{}{"alpha": "gamma", "one": "two"} 117 nodeOne.Update(optionsNew) 118 changes, err = nodeOne.Write() 119 c.Assert(err, jc.ErrorIsNil) 120 c.Assert(changes, gc.DeepEquals, []ItemChange{ 121 {ItemModified, "alpha", "beta", "gamma"}, 122 {ItemModified, "one", 1, "two"}, 123 }) 124 125 // Verify that node one reports as expected. 126 c.Assert(nodeOne.Map(), gc.DeepEquals, optionsNew) 127 128 // Verify that node two has still the old data. 129 c.Assert(nodeTwo.Map(), gc.DeepEquals, optionsOld) 130 131 // Now issue a Set/Write from node two. This will 132 // merge the data deleting 'one' and updating 133 // other values. 134 optionsMerge := map[string]interface{}{"alpha": "cappa", "new": "next"} 135 nodeTwo.Update(optionsMerge) 136 nodeTwo.Delete("one") 137 138 expected := map[string]interface{}{"alpha": "cappa", "new": "next"} 139 changes, err = nodeTwo.Write() 140 c.Assert(err, jc.ErrorIsNil) 141 c.Assert(changes, gc.DeepEquals, []ItemChange{ 142 {ItemModified, "alpha", "beta", "cappa"}, 143 {ItemAdded, "new", nil, "next"}, 144 {ItemDeleted, "one", 1, nil}, 145 }) 146 c.Assert(expected, gc.DeepEquals, nodeTwo.Map()) 147 148 // But node one still reflects the former data. 149 c.Assert(nodeOne.Map(), gc.DeepEquals, optionsNew) 150 } 151 152 func (s *SettingsSuite) TestSetItem(c *gc.C) { 153 // Check that Set works as expected. 154 node, err := s.createSettings(s.key, nil) 155 c.Assert(err, jc.ErrorIsNil) 156 options := map[string]interface{}{"alpha": "beta", "one": 1} 157 node.Set("alpha", "beta") 158 node.Set("one", 1) 159 changes, err := node.Write() 160 c.Assert(err, jc.ErrorIsNil) 161 c.Assert(changes, gc.DeepEquals, []ItemChange{ 162 {ItemAdded, "alpha", nil, "beta"}, 163 {ItemAdded, "one", nil, 1}, 164 }) 165 // Check local state. 166 c.Assert(node.Map(), gc.DeepEquals, options) 167 // Check MongoDB state. 168 var mgoData struct { 169 Settings settingsMap 170 } 171 settings, closer := s.state.getCollection(settingsC) 172 defer closer() 173 err = settings.FindId(s.key).One(&mgoData) 174 c.Assert(err, jc.ErrorIsNil) 175 c.Assert(map[string]interface{}(mgoData.Settings), gc.DeepEquals, options) 176 } 177 178 func (s *SettingsSuite) TestSetItemEscape(c *gc.C) { 179 // Check that Set works as expected. 180 node, err := s.createSettings(s.key, nil) 181 c.Assert(err, jc.ErrorIsNil) 182 options := map[string]interface{}{"$bar": 1, "foo.alpha": "beta"} 183 node.Set("foo.alpha", "beta") 184 node.Set("$bar", 1) 185 changes, err := node.Write() 186 c.Assert(err, jc.ErrorIsNil) 187 c.Assert(changes, gc.DeepEquals, []ItemChange{ 188 {ItemAdded, "$bar", nil, 1}, 189 {ItemAdded, "foo.alpha", nil, "beta"}, 190 }) 191 // Check local state. 192 c.Assert(node.Map(), gc.DeepEquals, options) 193 194 // Check MongoDB state. 195 mgoOptions := map[string]interface{}{"\uff04bar": 1, "foo\uff0ealpha": "beta"} 196 var mgoData struct { 197 Settings map[string]interface{} 198 } 199 settings, closer := s.state.getCollection(settingsC) 200 defer closer() 201 err = settings.FindId(s.key).One(&mgoData) 202 c.Assert(err, jc.ErrorIsNil) 203 c.Assert(mgoData.Settings, gc.DeepEquals, mgoOptions) 204 205 // Now get another state by reading from the database instance and 206 // check read state has replaced '.' and '$' after fetching from 207 // MongoDB. 208 nodeTwo, err := s.readSettings() 209 c.Assert(err, jc.ErrorIsNil) 210 c.Assert(nodeTwo.disk, gc.DeepEquals, options) 211 c.Assert(nodeTwo.core, gc.DeepEquals, options) 212 } 213 214 func (s *SettingsSuite) TestReplaceSettingsEscape(c *gc.C) { 215 // Check that replaceSettings works as expected. 216 node, err := s.createSettings(s.key, nil) 217 c.Assert(err, jc.ErrorIsNil) 218 node.Set("foo.alpha", "beta") 219 node.Set("$bar", 1) 220 _, err = node.Write() 221 c.Assert(err, jc.ErrorIsNil) 222 223 options := map[string]interface{}{"$baz": 1, "foo.bar": "beta"} 224 rop, settingsChanged, err := replaceSettingsOp(s.state, s.collection, s.key, options) 225 c.Assert(err, jc.ErrorIsNil) 226 ops := []txn.Op{rop} 227 err = node.st.runTransaction(ops) 228 c.Assert(err, jc.ErrorIsNil) 229 230 changed, err := settingsChanged() 231 c.Assert(err, jc.ErrorIsNil) 232 c.Assert(changed, jc.IsTrue) 233 234 // Check MongoDB state. 235 mgoOptions := map[string]interface{}{"\uff04baz": 1, "foo\uff0ebar": "beta"} 236 var mgoData struct { 237 Settings map[string]interface{} 238 } 239 settings, closer := s.state.getCollection(settingsC) 240 defer closer() 241 err = settings.FindId(s.key).One(&mgoData) 242 c.Assert(err, jc.ErrorIsNil) 243 c.Assert(mgoData.Settings, gc.DeepEquals, mgoOptions) 244 } 245 246 func (s *SettingsSuite) TestcreateSettingsEscape(c *gc.C) { 247 // Check that createSettings works as expected. 248 options := map[string]interface{}{"$baz": 1, "foo.bar": "beta"} 249 node, err := s.createSettings(s.key, options) 250 c.Assert(err, jc.ErrorIsNil) 251 252 // Check local state. 253 c.Assert(node.Map(), gc.DeepEquals, options) 254 255 // Check MongoDB state. 256 mgoOptions := map[string]interface{}{"\uff04baz": 1, "foo\uff0ebar": "beta"} 257 var mgoData struct { 258 Settings map[string]interface{} 259 } 260 settings, closer := s.state.getCollection(settingsC) 261 defer closer() 262 263 err = settings.FindId(s.key).One(&mgoData) 264 c.Assert(err, jc.ErrorIsNil) 265 c.Assert(mgoData.Settings, gc.DeepEquals, mgoOptions) 266 } 267 268 func (s *SettingsSuite) TestMultipleReads(c *gc.C) { 269 // Check that reads without writes always resets the data. 270 nodeOne, err := s.createSettings(s.key, nil) 271 c.Assert(err, jc.ErrorIsNil) 272 nodeOne.Update(map[string]interface{}{"alpha": "beta", "foo": "bar"}) 273 value, ok := nodeOne.Get("alpha") 274 c.Assert(ok, jc.IsTrue) 275 c.Assert(value, gc.Equals, "beta") 276 value, ok = nodeOne.Get("foo") 277 c.Assert(ok, jc.IsTrue) 278 c.Assert(value, gc.Equals, "bar") 279 value, ok = nodeOne.Get("baz") 280 c.Assert(ok, jc.IsFalse) 281 282 // A read resets the data to the empty state. 283 err = nodeOne.Read() 284 c.Assert(err, jc.ErrorIsNil) 285 c.Assert(nodeOne.Map(), gc.DeepEquals, map[string]interface{}{}) 286 nodeOne.Update(map[string]interface{}{"alpha": "beta", "foo": "bar"}) 287 changes, err := nodeOne.Write() 288 c.Assert(err, jc.ErrorIsNil) 289 c.Assert(changes, gc.DeepEquals, []ItemChange{ 290 {ItemAdded, "alpha", nil, "beta"}, 291 {ItemAdded, "foo", nil, "bar"}, 292 }) 293 294 // A write retains the newly set values. 295 value, ok = nodeOne.Get("alpha") 296 c.Assert(ok, jc.IsTrue) 297 c.Assert(value, gc.Equals, "beta") 298 value, ok = nodeOne.Get("foo") 299 c.Assert(ok, jc.IsTrue) 300 c.Assert(value, gc.Equals, "bar") 301 302 // Now get another state instance and change underlying state. 303 nodeTwo, err := s.readSettings() 304 c.Assert(err, jc.ErrorIsNil) 305 nodeTwo.Update(map[string]interface{}{"foo": "different"}) 306 changes, err = nodeTwo.Write() 307 c.Assert(err, jc.ErrorIsNil) 308 c.Assert(changes, gc.DeepEquals, []ItemChange{ 309 {ItemModified, "foo", "bar", "different"}, 310 }) 311 312 // This should pull in the new state into node one. 313 err = nodeOne.Read() 314 c.Assert(err, jc.ErrorIsNil) 315 value, ok = nodeOne.Get("alpha") 316 c.Assert(ok, jc.IsTrue) 317 c.Assert(value, gc.Equals, "beta") 318 value, ok = nodeOne.Get("foo") 319 c.Assert(ok, jc.IsTrue) 320 c.Assert(value, gc.Equals, "different") 321 } 322 323 func (s *SettingsSuite) TestDeleteEmptiesState(c *gc.C) { 324 node, err := s.createSettings(s.key, nil) 325 c.Assert(err, jc.ErrorIsNil) 326 node.Set("a", "foo") 327 changes, err := node.Write() 328 c.Assert(err, jc.ErrorIsNil) 329 c.Assert(changes, gc.DeepEquals, []ItemChange{ 330 {ItemAdded, "a", nil, "foo"}, 331 }) 332 node.Delete("a") 333 changes, err = node.Write() 334 c.Assert(err, jc.ErrorIsNil) 335 c.Assert(changes, gc.DeepEquals, []ItemChange{ 336 {ItemDeleted, "a", "foo", nil}, 337 }) 338 c.Assert(node.Map(), gc.DeepEquals, map[string]interface{}{}) 339 } 340 341 func (s *SettingsSuite) TestReadResync(c *gc.C) { 342 // Check that read pulls the data into the node. 343 nodeOne, err := s.createSettings(s.key, nil) 344 c.Assert(err, jc.ErrorIsNil) 345 nodeOne.Set("a", "foo") 346 changes, err := nodeOne.Write() 347 c.Assert(err, jc.ErrorIsNil) 348 c.Assert(changes, gc.DeepEquals, []ItemChange{ 349 {ItemAdded, "a", nil, "foo"}, 350 }) 351 nodeTwo, err := s.readSettings() 352 c.Assert(err, jc.ErrorIsNil) 353 nodeTwo.Delete("a") 354 changes, err = nodeTwo.Write() 355 c.Assert(err, jc.ErrorIsNil) 356 c.Assert(changes, gc.DeepEquals, []ItemChange{ 357 {ItemDeleted, "a", "foo", nil}, 358 }) 359 nodeTwo.Set("a", "bar") 360 changes, err = nodeTwo.Write() 361 c.Assert(err, jc.ErrorIsNil) 362 c.Assert(changes, gc.DeepEquals, []ItemChange{ 363 {ItemAdded, "a", nil, "bar"}, 364 }) 365 // Read of node one should pick up the new value. 366 err = nodeOne.Read() 367 c.Assert(err, jc.ErrorIsNil) 368 value, ok := nodeOne.Get("a") 369 c.Assert(ok, jc.IsTrue) 370 c.Assert(value, gc.Equals, "bar") 371 } 372 373 func (s *SettingsSuite) TestMultipleWrites(c *gc.C) { 374 // Check that multiple writes only do the right changes. 375 node, err := s.createSettings(s.key, nil) 376 c.Assert(err, jc.ErrorIsNil) 377 node.Update(map[string]interface{}{"foo": "bar", "this": "that"}) 378 changes, err := node.Write() 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(changes, gc.DeepEquals, []ItemChange{ 381 {ItemAdded, "foo", nil, "bar"}, 382 {ItemAdded, "this", nil, "that"}, 383 }) 384 node.Delete("this") 385 node.Set("another", "value") 386 changes, err = node.Write() 387 c.Assert(err, jc.ErrorIsNil) 388 c.Assert(changes, gc.DeepEquals, []ItemChange{ 389 {ItemAdded, "another", nil, "value"}, 390 {ItemDeleted, "this", "that", nil}, 391 }) 392 393 expected := map[string]interface{}{"foo": "bar", "another": "value"} 394 c.Assert(expected, gc.DeepEquals, node.Map()) 395 396 changes, err = node.Write() 397 c.Assert(err, jc.ErrorIsNil) 398 c.Assert(changes, gc.DeepEquals, []ItemChange{}) 399 400 err = node.Read() 401 c.Assert(err, jc.ErrorIsNil) 402 c.Assert(expected, gc.DeepEquals, node.Map()) 403 404 changes, err = node.Write() 405 c.Assert(err, jc.ErrorIsNil) 406 c.Assert(changes, gc.DeepEquals, []ItemChange{}) 407 } 408 409 func (s *SettingsSuite) TestMultipleWritesAreStable(c *gc.C) { 410 node, err := s.createSettings(s.key, nil) 411 c.Assert(err, jc.ErrorIsNil) 412 node.Update(map[string]interface{}{"foo": "bar", "this": "that"}) 413 _, err = node.Write() 414 c.Assert(err, jc.ErrorIsNil) 415 416 var mgoData struct { 417 Settings map[string]interface{} 418 } 419 settings, closer := s.state.getCollection(settingsC) 420 defer closer() 421 err = settings.FindId(s.key).One(&mgoData) 422 c.Assert(err, jc.ErrorIsNil) 423 version := mgoData.Settings["version"] 424 for i := 0; i < 100; i++ { 425 node.Set("value", i) 426 node.Set("foo", "bar") 427 node.Delete("value") 428 node.Set("this", "that") 429 _, err := node.Write() 430 c.Assert(err, jc.ErrorIsNil) 431 } 432 mgoData.Settings = make(map[string]interface{}) 433 err = settings.FindId(s.key).One(&mgoData) 434 c.Assert(err, jc.ErrorIsNil) 435 newVersion := mgoData.Settings["version"] 436 c.Assert(version, gc.Equals, newVersion) 437 } 438 439 func (s *SettingsSuite) TestWriteTwice(c *gc.C) { 440 // Check the correct writing into a node by two config nodes. 441 nodeOne, err := s.createSettings(s.key, nil) 442 c.Assert(err, jc.ErrorIsNil) 443 nodeOne.Set("a", "foo") 444 changes, err := nodeOne.Write() 445 c.Assert(err, jc.ErrorIsNil) 446 c.Assert(changes, gc.DeepEquals, []ItemChange{ 447 {ItemAdded, "a", nil, "foo"}, 448 }) 449 450 nodeTwo, err := s.readSettings() 451 c.Assert(err, jc.ErrorIsNil) 452 nodeTwo.Set("a", "bar") 453 changes, err = nodeTwo.Write() 454 c.Assert(err, jc.ErrorIsNil) 455 c.Assert(changes, gc.DeepEquals, []ItemChange{ 456 {ItemModified, "a", "foo", "bar"}, 457 }) 458 459 // Shouldn't write again. Changes were already 460 // flushed and acted upon by other parties. 461 changes, err = nodeOne.Write() 462 c.Assert(err, jc.ErrorIsNil) 463 c.Assert(changes, gc.DeepEquals, []ItemChange{}) 464 465 err = nodeOne.Read() 466 c.Assert(err, jc.ErrorIsNil) 467 c.Assert(nodeOne.key, gc.Equals, nodeTwo.key) 468 c.Assert(nodeOne.disk, gc.DeepEquals, nodeTwo.disk) 469 c.Assert(nodeOne.core, gc.DeepEquals, nodeTwo.core) 470 } 471 472 func (s *SettingsSuite) TestList(c *gc.C) { 473 _, err := s.createSettings("key#1", map[string]interface{}{"foo1": "bar1"}) 474 c.Assert(err, jc.ErrorIsNil) 475 _, err = s.createSettings("key#2", map[string]interface{}{"foo2": "bar2"}) 476 c.Assert(err, jc.ErrorIsNil) 477 _, err = s.createSettings("another#1", map[string]interface{}{"foo2": "bar2"}) 478 c.Assert(err, jc.ErrorIsNil) 479 480 nodes, err := listSettings(s.state, s.collection, "key#") 481 c.Assert(err, jc.ErrorIsNil) 482 c.Assert(nodes, jc.DeepEquals, map[string]map[string]interface{}{ 483 "key#1": {"foo1": "bar1"}, 484 "key#2": {"foo2": "bar2"}, 485 }) 486 }