1// Copyright 2015 The etcd Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package store 16 17import ( 18 "testing" 19 "time" 20 21 etcdErr "github.com/coreos/etcd/error" 22 "github.com/coreos/etcd/pkg/testutil" 23 "github.com/jonboulle/clockwork" 24) 25 26func TestNewStoreWithNamespaces(t *testing.T) { 27 s := newStore("/0", "/1") 28 29 _, err := s.Get("/0", false, false) 30 testutil.AssertNil(t, err) 31 _, err = s.Get("/1", false, false) 32 testutil.AssertNil(t, err) 33} 34 35// Ensure that the store can retrieve an existing value. 36func TestStoreGetValue(t *testing.T) { 37 s := newStore() 38 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 39 var eidx uint64 = 1 40 e, err := s.Get("/foo", false, false) 41 testutil.AssertNil(t, err) 42 testutil.AssertEqual(t, e.EtcdIndex, eidx) 43 testutil.AssertEqual(t, e.Action, "get") 44 testutil.AssertEqual(t, e.Node.Key, "/foo") 45 testutil.AssertEqual(t, *e.Node.Value, "bar") 46} 47 48// Ensure that any TTL <= minExpireTime becomes Permanent 49func TestMinExpireTime(t *testing.T) { 50 s := newStore() 51 fc := clockwork.NewFakeClock() 52 s.clock = fc 53 // FakeClock starts at 0, so minExpireTime should be far in the future.. but just in case 54 testutil.AssertTrue(t, minExpireTime.After(fc.Now()), "minExpireTime should be ahead of FakeClock!") 55 s.Create("/foo", false, "Y", false, TTLOptionSet{ExpireTime: fc.Now().Add(3 * time.Second)}) 56 fc.Advance(5 * time.Second) 57 // Ensure it hasn't expired 58 s.DeleteExpiredKeys(fc.Now()) 59 var eidx uint64 = 1 60 e, err := s.Get("/foo", true, false) 61 testutil.AssertNil(t, err) 62 testutil.AssertEqual(t, e.EtcdIndex, eidx) 63 testutil.AssertEqual(t, e.Action, "get") 64 testutil.AssertEqual(t, e.Node.Key, "/foo") 65 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 66} 67 68// Ensure that the store can recursively retrieve a directory listing. 69// Note that hidden files should not be returned. 70func TestStoreGetDirectory(t *testing.T) { 71 s := newStore() 72 fc := newFakeClock() 73 s.clock = fc 74 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 75 s.Create("/foo/bar", false, "X", false, TTLOptionSet{ExpireTime: Permanent}) 76 s.Create("/foo/_hidden", false, "*", false, TTLOptionSet{ExpireTime: Permanent}) 77 s.Create("/foo/baz", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 78 s.Create("/foo/baz/bat", false, "Y", false, TTLOptionSet{ExpireTime: Permanent}) 79 s.Create("/foo/baz/_hidden", false, "*", false, TTLOptionSet{ExpireTime: Permanent}) 80 s.Create("/foo/baz/ttl", false, "Y", false, TTLOptionSet{ExpireTime: fc.Now().Add(time.Second * 3)}) 81 var eidx uint64 = 7 82 e, err := s.Get("/foo", true, false) 83 testutil.AssertNil(t, err) 84 testutil.AssertEqual(t, e.EtcdIndex, eidx) 85 testutil.AssertEqual(t, e.Action, "get") 86 testutil.AssertEqual(t, e.Node.Key, "/foo") 87 testutil.AssertEqual(t, len(e.Node.Nodes), 2) 88 var bazNodes NodeExterns 89 for _, node := range e.Node.Nodes { 90 switch node.Key { 91 case "/foo/bar": 92 testutil.AssertEqual(t, *node.Value, "X") 93 testutil.AssertEqual(t, node.Dir, false) 94 case "/foo/baz": 95 testutil.AssertEqual(t, node.Dir, true) 96 testutil.AssertEqual(t, len(node.Nodes), 2) 97 bazNodes = node.Nodes 98 default: 99 t.Errorf("key = %s, not matched", node.Key) 100 } 101 } 102 for _, node := range bazNodes { 103 switch node.Key { 104 case "/foo/baz/bat": 105 testutil.AssertEqual(t, *node.Value, "Y") 106 testutil.AssertEqual(t, node.Dir, false) 107 case "/foo/baz/ttl": 108 testutil.AssertEqual(t, *node.Value, "Y") 109 testutil.AssertEqual(t, node.Dir, false) 110 testutil.AssertEqual(t, node.TTL, int64(3)) 111 default: 112 t.Errorf("key = %s, not matched", node.Key) 113 } 114 } 115} 116 117// Ensure that the store can retrieve a directory in sorted order. 118func TestStoreGetSorted(t *testing.T) { 119 s := newStore() 120 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 121 s.Create("/foo/x", false, "0", false, TTLOptionSet{ExpireTime: Permanent}) 122 s.Create("/foo/z", false, "0", false, TTLOptionSet{ExpireTime: Permanent}) 123 s.Create("/foo/y", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 124 s.Create("/foo/y/a", false, "0", false, TTLOptionSet{ExpireTime: Permanent}) 125 s.Create("/foo/y/b", false, "0", false, TTLOptionSet{ExpireTime: Permanent}) 126 var eidx uint64 = 6 127 e, err := s.Get("/foo", true, true) 128 testutil.AssertNil(t, err) 129 testutil.AssertEqual(t, e.EtcdIndex, eidx) 130 131 var yNodes NodeExterns 132 sortedStrings := []string{"/foo/x", "/foo/y", "/foo/z"} 133 for i := range e.Node.Nodes { 134 node := e.Node.Nodes[i] 135 if node.Key != sortedStrings[i] { 136 t.Errorf("expect key = %s, got key = %s", sortedStrings[i], node.Key) 137 } 138 if node.Key == "/foo/y" { 139 yNodes = node.Nodes 140 } 141 } 142 143 sortedStrings = []string{"/foo/y/a", "/foo/y/b"} 144 for i := range yNodes { 145 node := yNodes[i] 146 if node.Key != sortedStrings[i] { 147 t.Errorf("expect key = %s, got key = %s", sortedStrings[i], node.Key) 148 } 149 } 150} 151 152func TestSet(t *testing.T) { 153 s := newStore() 154 155 // Set /foo="" 156 var eidx uint64 = 1 157 e, err := s.Set("/foo", false, "", TTLOptionSet{ExpireTime: Permanent}) 158 testutil.AssertNil(t, err) 159 testutil.AssertEqual(t, e.EtcdIndex, eidx) 160 testutil.AssertEqual(t, e.Action, "set") 161 testutil.AssertEqual(t, e.Node.Key, "/foo") 162 testutil.AssertFalse(t, e.Node.Dir) 163 testutil.AssertEqual(t, *e.Node.Value, "") 164 testutil.AssertNil(t, e.Node.Nodes) 165 testutil.AssertNil(t, e.Node.Expiration) 166 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 167 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(1)) 168 169 // Set /foo="bar" 170 eidx = 2 171 e, err = s.Set("/foo", false, "bar", TTLOptionSet{ExpireTime: Permanent}) 172 testutil.AssertNil(t, err) 173 testutil.AssertEqual(t, e.EtcdIndex, eidx) 174 testutil.AssertEqual(t, e.Action, "set") 175 testutil.AssertEqual(t, e.Node.Key, "/foo") 176 testutil.AssertFalse(t, e.Node.Dir) 177 testutil.AssertEqual(t, *e.Node.Value, "bar") 178 testutil.AssertNil(t, e.Node.Nodes) 179 testutil.AssertNil(t, e.Node.Expiration) 180 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 181 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(2)) 182 // check prevNode 183 testutil.AssertNotNil(t, e.PrevNode) 184 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 185 testutil.AssertEqual(t, *e.PrevNode.Value, "") 186 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 187 // Set /foo="baz" (for testing prevNode) 188 eidx = 3 189 e, err = s.Set("/foo", false, "baz", TTLOptionSet{ExpireTime: Permanent}) 190 testutil.AssertNil(t, err) 191 testutil.AssertEqual(t, e.EtcdIndex, eidx) 192 testutil.AssertEqual(t, e.Action, "set") 193 testutil.AssertEqual(t, e.Node.Key, "/foo") 194 testutil.AssertFalse(t, e.Node.Dir) 195 testutil.AssertEqual(t, *e.Node.Value, "baz") 196 testutil.AssertNil(t, e.Node.Nodes) 197 testutil.AssertNil(t, e.Node.Expiration) 198 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 199 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(3)) 200 // check prevNode 201 testutil.AssertNotNil(t, e.PrevNode) 202 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 203 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 204 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(2)) 205 206 // Set /dir as a directory 207 eidx = 4 208 e, err = s.Set("/dir", true, "", TTLOptionSet{ExpireTime: Permanent}) 209 testutil.AssertNil(t, err) 210 testutil.AssertEqual(t, e.EtcdIndex, eidx) 211 testutil.AssertEqual(t, e.Action, "set") 212 testutil.AssertEqual(t, e.Node.Key, "/dir") 213 testutil.AssertTrue(t, e.Node.Dir) 214 testutil.AssertNil(t, e.Node.Value) 215 testutil.AssertNil(t, e.Node.Nodes) 216 testutil.AssertNil(t, e.Node.Expiration) 217 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 218 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(4)) 219} 220 221// Ensure that the store can create a new key if it doesn't already exist. 222func TestStoreCreateValue(t *testing.T) { 223 s := newStore() 224 // Create /foo=bar 225 var eidx uint64 = 1 226 e, err := s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 227 testutil.AssertNil(t, err) 228 testutil.AssertEqual(t, e.EtcdIndex, eidx) 229 testutil.AssertEqual(t, e.Action, "create") 230 testutil.AssertEqual(t, e.Node.Key, "/foo") 231 testutil.AssertFalse(t, e.Node.Dir) 232 testutil.AssertEqual(t, *e.Node.Value, "bar") 233 testutil.AssertNil(t, e.Node.Nodes) 234 testutil.AssertNil(t, e.Node.Expiration) 235 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 236 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(1)) 237 238 // Create /empty="" 239 eidx = 2 240 e, err = s.Create("/empty", false, "", false, TTLOptionSet{ExpireTime: Permanent}) 241 testutil.AssertNil(t, err) 242 testutil.AssertEqual(t, e.EtcdIndex, eidx) 243 testutil.AssertEqual(t, e.Action, "create") 244 testutil.AssertEqual(t, e.Node.Key, "/empty") 245 testutil.AssertFalse(t, e.Node.Dir) 246 testutil.AssertEqual(t, *e.Node.Value, "") 247 testutil.AssertNil(t, e.Node.Nodes) 248 testutil.AssertNil(t, e.Node.Expiration) 249 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 250 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(2)) 251 252} 253 254// Ensure that the store can create a new directory if it doesn't already exist. 255func TestStoreCreateDirectory(t *testing.T) { 256 s := newStore() 257 var eidx uint64 = 1 258 e, err := s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 259 testutil.AssertNil(t, err) 260 testutil.AssertEqual(t, e.EtcdIndex, eidx) 261 testutil.AssertEqual(t, e.Action, "create") 262 testutil.AssertEqual(t, e.Node.Key, "/foo") 263 testutil.AssertTrue(t, e.Node.Dir) 264} 265 266// Ensure that the store fails to create a key if it already exists. 267func TestStoreCreateFailsIfExists(t *testing.T) { 268 s := newStore() 269 // create /foo as dir 270 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 271 272 // create /foo as dir again 273 e, _err := s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 274 err := _err.(*etcdErr.Error) 275 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeNodeExist) 276 testutil.AssertEqual(t, err.Message, "Key already exists") 277 testutil.AssertEqual(t, err.Cause, "/foo") 278 testutil.AssertEqual(t, err.Index, uint64(1)) 279 testutil.AssertNil(t, e) 280} 281 282// Ensure that the store can update a key if it already exists. 283func TestStoreUpdateValue(t *testing.T) { 284 s := newStore() 285 // create /foo=bar 286 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 287 // update /foo="bzr" 288 var eidx uint64 = 2 289 e, err := s.Update("/foo", "baz", TTLOptionSet{ExpireTime: Permanent}) 290 testutil.AssertNil(t, err) 291 testutil.AssertEqual(t, e.EtcdIndex, eidx) 292 testutil.AssertEqual(t, e.Action, "update") 293 testutil.AssertEqual(t, e.Node.Key, "/foo") 294 testutil.AssertFalse(t, e.Node.Dir) 295 testutil.AssertEqual(t, *e.Node.Value, "baz") 296 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 297 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(2)) 298 // check prevNode 299 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 300 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 301 testutil.AssertEqual(t, e.PrevNode.TTL, int64(0)) 302 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 303 304 e, _ = s.Get("/foo", false, false) 305 testutil.AssertEqual(t, *e.Node.Value, "baz") 306 testutil.AssertEqual(t, e.EtcdIndex, eidx) 307 308 // update /foo="" 309 eidx = 3 310 e, err = s.Update("/foo", "", TTLOptionSet{ExpireTime: Permanent}) 311 testutil.AssertNil(t, err) 312 testutil.AssertEqual(t, e.EtcdIndex, eidx) 313 testutil.AssertEqual(t, e.Action, "update") 314 testutil.AssertEqual(t, e.Node.Key, "/foo") 315 testutil.AssertFalse(t, e.Node.Dir) 316 testutil.AssertEqual(t, *e.Node.Value, "") 317 testutil.AssertEqual(t, e.Node.TTL, int64(0)) 318 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(3)) 319 // check prevNode 320 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 321 testutil.AssertEqual(t, *e.PrevNode.Value, "baz") 322 testutil.AssertEqual(t, e.PrevNode.TTL, int64(0)) 323 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(2)) 324 325 e, _ = s.Get("/foo", false, false) 326 testutil.AssertEqual(t, e.EtcdIndex, eidx) 327 testutil.AssertEqual(t, *e.Node.Value, "") 328} 329 330// Ensure that the store cannot update a directory. 331func TestStoreUpdateFailsIfDirectory(t *testing.T) { 332 s := newStore() 333 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 334 e, _err := s.Update("/foo", "baz", TTLOptionSet{ExpireTime: Permanent}) 335 err := _err.(*etcdErr.Error) 336 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeNotFile) 337 testutil.AssertEqual(t, err.Message, "Not a file") 338 testutil.AssertEqual(t, err.Cause, "/foo") 339 testutil.AssertNil(t, e) 340} 341 342// Ensure that the store can update the TTL on a value. 343func TestStoreUpdateValueTTL(t *testing.T) { 344 s := newStore() 345 fc := newFakeClock() 346 s.clock = fc 347 348 var eidx uint64 = 2 349 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 350 _, err := s.Update("/foo", "baz", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 351 testutil.AssertNil(t, err) 352 e, _ := s.Get("/foo", false, false) 353 testutil.AssertEqual(t, *e.Node.Value, "baz") 354 testutil.AssertEqual(t, e.EtcdIndex, eidx) 355 fc.Advance(600 * time.Millisecond) 356 s.DeleteExpiredKeys(fc.Now()) 357 e, err = s.Get("/foo", false, false) 358 testutil.AssertNil(t, e) 359 testutil.AssertEqual(t, err.(*etcdErr.Error).ErrorCode, etcdErr.EcodeKeyNotFound) 360} 361 362// Ensure that the store can update the TTL on a directory. 363func TestStoreUpdateDirTTL(t *testing.T) { 364 s := newStore() 365 fc := newFakeClock() 366 s.clock = fc 367 368 var eidx uint64 = 3 369 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 370 s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 371 e, err := s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 372 testutil.AssertNil(t, err) 373 testutil.AssertEqual(t, e.Node.Dir, true) 374 testutil.AssertEqual(t, e.EtcdIndex, eidx) 375 e, _ = s.Get("/foo/bar", false, false) 376 testutil.AssertEqual(t, *e.Node.Value, "baz") 377 testutil.AssertEqual(t, e.EtcdIndex, eidx) 378 379 fc.Advance(600 * time.Millisecond) 380 s.DeleteExpiredKeys(fc.Now()) 381 e, err = s.Get("/foo/bar", false, false) 382 testutil.AssertNil(t, e) 383 testutil.AssertEqual(t, err.(*etcdErr.Error).ErrorCode, etcdErr.EcodeKeyNotFound) 384} 385 386// Ensure that the store can delete a value. 387func TestStoreDeleteValue(t *testing.T) { 388 s := newStore() 389 var eidx uint64 = 2 390 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 391 e, err := s.Delete("/foo", false, false) 392 testutil.AssertNil(t, err) 393 testutil.AssertEqual(t, e.EtcdIndex, eidx) 394 testutil.AssertEqual(t, e.Action, "delete") 395 // check prevNode 396 testutil.AssertNotNil(t, e.PrevNode) 397 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 398 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 399} 400 401// Ensure that the store can delete a directory if recursive is specified. 402func TestStoreDeleteDiretory(t *testing.T) { 403 s := newStore() 404 // create directory /foo 405 var eidx uint64 = 2 406 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 407 // delete /foo with dir = true and recursive = false 408 // this should succeed, since the directory is empty 409 e, err := s.Delete("/foo", true, false) 410 testutil.AssertNil(t, err) 411 testutil.AssertEqual(t, e.EtcdIndex, eidx) 412 testutil.AssertEqual(t, e.Action, "delete") 413 // check prevNode 414 testutil.AssertNotNil(t, e.PrevNode) 415 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 416 testutil.AssertEqual(t, e.PrevNode.Dir, true) 417 418 // create directory /foo and directory /foo/bar 419 s.Create("/foo/bar", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 420 // delete /foo with dir = true and recursive = false 421 // this should fail, since the directory is not empty 422 _, err = s.Delete("/foo", true, false) 423 testutil.AssertNotNil(t, err) 424 425 // delete /foo with dir=false and recursive = true 426 // this should succeed, since recursive implies dir=true 427 // and recursively delete should be able to delete all 428 // items under the given directory 429 e, err = s.Delete("/foo", false, true) 430 testutil.AssertNil(t, err) 431 testutil.AssertEqual(t, e.Action, "delete") 432 433} 434 435// Ensure that the store cannot delete a directory if both of recursive 436// and dir are not specified. 437func TestStoreDeleteDiretoryFailsIfNonRecursiveAndDir(t *testing.T) { 438 s := newStore() 439 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 440 e, _err := s.Delete("/foo", false, false) 441 err := _err.(*etcdErr.Error) 442 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeNotFile) 443 testutil.AssertEqual(t, err.Message, "Not a file") 444 testutil.AssertNil(t, e) 445} 446 447func TestRootRdOnly(t *testing.T) { 448 s := newStore("/0") 449 450 for _, tt := range []string{"/", "/0"} { 451 _, err := s.Set(tt, true, "", TTLOptionSet{ExpireTime: Permanent}) 452 testutil.AssertNotNil(t, err) 453 454 _, err = s.Delete(tt, true, true) 455 testutil.AssertNotNil(t, err) 456 457 _, err = s.Create(tt, true, "", false, TTLOptionSet{ExpireTime: Permanent}) 458 testutil.AssertNotNil(t, err) 459 460 _, err = s.Update(tt, "", TTLOptionSet{ExpireTime: Permanent}) 461 testutil.AssertNotNil(t, err) 462 463 _, err = s.CompareAndSwap(tt, "", 0, "", TTLOptionSet{ExpireTime: Permanent}) 464 testutil.AssertNotNil(t, err) 465 } 466} 467 468func TestStoreCompareAndDeletePrevValue(t *testing.T) { 469 s := newStore() 470 var eidx uint64 = 2 471 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 472 e, err := s.CompareAndDelete("/foo", "bar", 0) 473 testutil.AssertNil(t, err) 474 testutil.AssertEqual(t, e.EtcdIndex, eidx) 475 testutil.AssertEqual(t, e.Action, "compareAndDelete") 476 testutil.AssertEqual(t, e.Node.Key, "/foo") 477 478 // check prevNode 479 testutil.AssertNotNil(t, e.PrevNode) 480 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 481 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 482 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 483 testutil.AssertEqual(t, e.PrevNode.CreatedIndex, uint64(1)) 484} 485 486func TestStoreCompareAndDeletePrevValueFailsIfNotMatch(t *testing.T) { 487 s := newStore() 488 var eidx uint64 = 1 489 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 490 e, _err := s.CompareAndDelete("/foo", "baz", 0) 491 err := _err.(*etcdErr.Error) 492 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeTestFailed) 493 testutil.AssertEqual(t, err.Message, "Compare failed") 494 testutil.AssertNil(t, e) 495 e, _ = s.Get("/foo", false, false) 496 testutil.AssertEqual(t, e.EtcdIndex, eidx) 497 testutil.AssertEqual(t, *e.Node.Value, "bar") 498} 499 500func TestStoreCompareAndDeletePrevIndex(t *testing.T) { 501 s := newStore() 502 var eidx uint64 = 2 503 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 504 e, err := s.CompareAndDelete("/foo", "", 1) 505 testutil.AssertNil(t, err) 506 testutil.AssertEqual(t, e.EtcdIndex, eidx) 507 testutil.AssertEqual(t, e.Action, "compareAndDelete") 508 // check prevNode 509 testutil.AssertNotNil(t, e.PrevNode) 510 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 511 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 512 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 513 testutil.AssertEqual(t, e.PrevNode.CreatedIndex, uint64(1)) 514} 515 516func TestStoreCompareAndDeletePrevIndexFailsIfNotMatch(t *testing.T) { 517 s := newStore() 518 var eidx uint64 = 1 519 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 520 e, _err := s.CompareAndDelete("/foo", "", 100) 521 testutil.AssertNotNil(t, _err) 522 err := _err.(*etcdErr.Error) 523 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeTestFailed) 524 testutil.AssertEqual(t, err.Message, "Compare failed") 525 testutil.AssertNil(t, e) 526 e, _ = s.Get("/foo", false, false) 527 testutil.AssertEqual(t, e.EtcdIndex, eidx) 528 testutil.AssertEqual(t, *e.Node.Value, "bar") 529} 530 531// Ensure that the store cannot delete a directory. 532func TestStoreCompareAndDeleteDiretoryFail(t *testing.T) { 533 s := newStore() 534 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 535 _, _err := s.CompareAndDelete("/foo", "", 0) 536 testutil.AssertNotNil(t, _err) 537 err := _err.(*etcdErr.Error) 538 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeNotFile) 539} 540 541// Ensure that the store can conditionally update a key if it has a previous value. 542func TestStoreCompareAndSwapPrevValue(t *testing.T) { 543 s := newStore() 544 var eidx uint64 = 2 545 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 546 e, err := s.CompareAndSwap("/foo", "bar", 0, "baz", TTLOptionSet{ExpireTime: Permanent}) 547 testutil.AssertNil(t, err) 548 testutil.AssertEqual(t, e.EtcdIndex, eidx) 549 testutil.AssertEqual(t, e.Action, "compareAndSwap") 550 testutil.AssertEqual(t, *e.Node.Value, "baz") 551 // check prevNode 552 testutil.AssertNotNil(t, e.PrevNode) 553 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 554 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 555 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 556 testutil.AssertEqual(t, e.PrevNode.CreatedIndex, uint64(1)) 557 558 e, _ = s.Get("/foo", false, false) 559 testutil.AssertEqual(t, *e.Node.Value, "baz") 560} 561 562// Ensure that the store cannot conditionally update a key if it has the wrong previous value. 563func TestStoreCompareAndSwapPrevValueFailsIfNotMatch(t *testing.T) { 564 s := newStore() 565 var eidx uint64 = 1 566 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 567 e, _err := s.CompareAndSwap("/foo", "wrong_value", 0, "baz", TTLOptionSet{ExpireTime: Permanent}) 568 err := _err.(*etcdErr.Error) 569 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeTestFailed) 570 testutil.AssertEqual(t, err.Message, "Compare failed") 571 testutil.AssertNil(t, e) 572 e, _ = s.Get("/foo", false, false) 573 testutil.AssertEqual(t, *e.Node.Value, "bar") 574 testutil.AssertEqual(t, e.EtcdIndex, eidx) 575} 576 577// Ensure that the store can conditionally update a key if it has a previous index. 578func TestStoreCompareAndSwapPrevIndex(t *testing.T) { 579 s := newStore() 580 var eidx uint64 = 2 581 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 582 e, err := s.CompareAndSwap("/foo", "", 1, "baz", TTLOptionSet{ExpireTime: Permanent}) 583 testutil.AssertNil(t, err) 584 testutil.AssertEqual(t, e.EtcdIndex, eidx) 585 testutil.AssertEqual(t, e.Action, "compareAndSwap") 586 testutil.AssertEqual(t, *e.Node.Value, "baz") 587 // check prevNode 588 testutil.AssertNotNil(t, e.PrevNode) 589 testutil.AssertEqual(t, e.PrevNode.Key, "/foo") 590 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 591 testutil.AssertEqual(t, e.PrevNode.ModifiedIndex, uint64(1)) 592 testutil.AssertEqual(t, e.PrevNode.CreatedIndex, uint64(1)) 593 594 e, _ = s.Get("/foo", false, false) 595 testutil.AssertEqual(t, *e.Node.Value, "baz") 596 testutil.AssertEqual(t, e.EtcdIndex, eidx) 597} 598 599// Ensure that the store cannot conditionally update a key if it has the wrong previous index. 600func TestStoreCompareAndSwapPrevIndexFailsIfNotMatch(t *testing.T) { 601 s := newStore() 602 var eidx uint64 = 1 603 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 604 e, _err := s.CompareAndSwap("/foo", "", 100, "baz", TTLOptionSet{ExpireTime: Permanent}) 605 err := _err.(*etcdErr.Error) 606 testutil.AssertEqual(t, err.ErrorCode, etcdErr.EcodeTestFailed) 607 testutil.AssertEqual(t, err.Message, "Compare failed") 608 testutil.AssertNil(t, e) 609 e, _ = s.Get("/foo", false, false) 610 testutil.AssertEqual(t, e.EtcdIndex, eidx) 611 testutil.AssertEqual(t, *e.Node.Value, "bar") 612} 613 614// Ensure that the store can watch for key creation. 615func TestStoreWatchCreate(t *testing.T) { 616 s := newStore() 617 var eidx uint64 = 0 618 w, _ := s.Watch("/foo", false, false, 0) 619 c := w.EventChan() 620 testutil.AssertEqual(t, w.StartIndex(), eidx) 621 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 622 eidx = 1 623 e := nbselect(c) 624 testutil.AssertEqual(t, e.EtcdIndex, eidx) 625 testutil.AssertEqual(t, e.Action, "create") 626 testutil.AssertEqual(t, e.Node.Key, "/foo") 627 e = nbselect(c) 628 testutil.AssertNil(t, e) 629} 630 631// Ensure that the store can watch for recursive key creation. 632func TestStoreWatchRecursiveCreate(t *testing.T) { 633 s := newStore() 634 var eidx uint64 = 0 635 w, _ := s.Watch("/foo", true, false, 0) 636 testutil.AssertEqual(t, w.StartIndex(), eidx) 637 eidx = 1 638 s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 639 e := nbselect(w.EventChan()) 640 testutil.AssertEqual(t, e.EtcdIndex, eidx) 641 testutil.AssertEqual(t, e.Action, "create") 642 testutil.AssertEqual(t, e.Node.Key, "/foo/bar") 643} 644 645// Ensure that the store can watch for key updates. 646func TestStoreWatchUpdate(t *testing.T) { 647 s := newStore() 648 var eidx uint64 = 1 649 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 650 w, _ := s.Watch("/foo", false, false, 0) 651 testutil.AssertEqual(t, w.StartIndex(), eidx) 652 eidx = 2 653 s.Update("/foo", "baz", TTLOptionSet{ExpireTime: Permanent}) 654 e := nbselect(w.EventChan()) 655 testutil.AssertEqual(t, e.EtcdIndex, eidx) 656 testutil.AssertEqual(t, e.Action, "update") 657 testutil.AssertEqual(t, e.Node.Key, "/foo") 658} 659 660// Ensure that the store can watch for recursive key updates. 661func TestStoreWatchRecursiveUpdate(t *testing.T) { 662 s := newStore() 663 var eidx uint64 = 1 664 s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 665 w, _ := s.Watch("/foo", true, false, 0) 666 testutil.AssertEqual(t, w.StartIndex(), eidx) 667 eidx = 2 668 s.Update("/foo/bar", "baz", TTLOptionSet{ExpireTime: Permanent}) 669 e := nbselect(w.EventChan()) 670 testutil.AssertEqual(t, e.EtcdIndex, eidx) 671 testutil.AssertEqual(t, e.Action, "update") 672 testutil.AssertEqual(t, e.Node.Key, "/foo/bar") 673} 674 675// Ensure that the store can watch for key deletions. 676func TestStoreWatchDelete(t *testing.T) { 677 s := newStore() 678 var eidx uint64 = 1 679 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 680 w, _ := s.Watch("/foo", false, false, 0) 681 testutil.AssertEqual(t, w.StartIndex(), eidx) 682 eidx = 2 683 s.Delete("/foo", false, false) 684 e := nbselect(w.EventChan()) 685 testutil.AssertEqual(t, e.EtcdIndex, eidx) 686 testutil.AssertEqual(t, e.Action, "delete") 687 testutil.AssertEqual(t, e.Node.Key, "/foo") 688} 689 690// Ensure that the store can watch for recursive key deletions. 691func TestStoreWatchRecursiveDelete(t *testing.T) { 692 s := newStore() 693 var eidx uint64 = 1 694 s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 695 w, _ := s.Watch("/foo", true, false, 0) 696 testutil.AssertEqual(t, w.StartIndex(), eidx) 697 eidx = 2 698 s.Delete("/foo/bar", false, false) 699 e := nbselect(w.EventChan()) 700 testutil.AssertEqual(t, e.EtcdIndex, eidx) 701 testutil.AssertEqual(t, e.Action, "delete") 702 testutil.AssertEqual(t, e.Node.Key, "/foo/bar") 703} 704 705// Ensure that the store can watch for CAS updates. 706func TestStoreWatchCompareAndSwap(t *testing.T) { 707 s := newStore() 708 var eidx uint64 = 1 709 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 710 w, _ := s.Watch("/foo", false, false, 0) 711 testutil.AssertEqual(t, w.StartIndex(), eidx) 712 eidx = 2 713 s.CompareAndSwap("/foo", "bar", 0, "baz", TTLOptionSet{ExpireTime: Permanent}) 714 e := nbselect(w.EventChan()) 715 testutil.AssertEqual(t, e.EtcdIndex, eidx) 716 testutil.AssertEqual(t, e.Action, "compareAndSwap") 717 testutil.AssertEqual(t, e.Node.Key, "/foo") 718} 719 720// Ensure that the store can watch for recursive CAS updates. 721func TestStoreWatchRecursiveCompareAndSwap(t *testing.T) { 722 s := newStore() 723 var eidx uint64 = 1 724 s.Create("/foo/bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 725 w, _ := s.Watch("/foo", true, false, 0) 726 testutil.AssertEqual(t, w.StartIndex(), eidx) 727 eidx = 2 728 s.CompareAndSwap("/foo/bar", "baz", 0, "bat", TTLOptionSet{ExpireTime: Permanent}) 729 e := nbselect(w.EventChan()) 730 testutil.AssertEqual(t, e.EtcdIndex, eidx) 731 testutil.AssertEqual(t, e.Action, "compareAndSwap") 732 testutil.AssertEqual(t, e.Node.Key, "/foo/bar") 733} 734 735// Ensure that the store can watch for key expiration. 736func TestStoreWatchExpire(t *testing.T) { 737 s := newStore() 738 fc := newFakeClock() 739 s.clock = fc 740 741 var eidx uint64 = 3 742 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(400 * time.Millisecond)}) 743 s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(450 * time.Millisecond)}) 744 s.Create("/foodir", true, "", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 745 746 w, _ := s.Watch("/", true, false, 0) 747 testutil.AssertEqual(t, w.StartIndex(), eidx) 748 c := w.EventChan() 749 e := nbselect(c) 750 testutil.AssertNil(t, e) 751 fc.Advance(600 * time.Millisecond) 752 s.DeleteExpiredKeys(fc.Now()) 753 eidx = 4 754 e = nbselect(c) 755 testutil.AssertEqual(t, e.EtcdIndex, eidx) 756 testutil.AssertEqual(t, e.Action, "expire") 757 testutil.AssertEqual(t, e.Node.Key, "/foo") 758 w, _ = s.Watch("/", true, false, 5) 759 eidx = 6 760 testutil.AssertEqual(t, w.StartIndex(), eidx) 761 e = nbselect(w.EventChan()) 762 testutil.AssertEqual(t, e.EtcdIndex, eidx) 763 testutil.AssertEqual(t, e.Action, "expire") 764 testutil.AssertEqual(t, e.Node.Key, "/foofoo") 765 w, _ = s.Watch("/", true, false, 6) 766 e = nbselect(w.EventChan()) 767 testutil.AssertEqual(t, e.EtcdIndex, eidx) 768 testutil.AssertEqual(t, e.Action, "expire") 769 testutil.AssertEqual(t, e.Node.Key, "/foodir") 770 testutil.AssertEqual(t, e.Node.Dir, true) 771} 772 773// Ensure that the store can watch for key expiration when refreshing. 774func TestStoreWatchExpireRefresh(t *testing.T) { 775 s := newStore() 776 fc := newFakeClock() 777 s.clock = fc 778 779 var eidx uint64 = 2 780 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 781 s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(1200 * time.Millisecond), Refresh: true}) 782 783 // Make sure we set watch updates when Refresh is true for newly created keys 784 w, _ := s.Watch("/", true, false, 0) 785 testutil.AssertEqual(t, w.StartIndex(), eidx) 786 c := w.EventChan() 787 e := nbselect(c) 788 testutil.AssertNil(t, e) 789 fc.Advance(600 * time.Millisecond) 790 s.DeleteExpiredKeys(fc.Now()) 791 eidx = 3 792 e = nbselect(c) 793 testutil.AssertEqual(t, e.EtcdIndex, eidx) 794 testutil.AssertEqual(t, e.Action, "expire") 795 testutil.AssertEqual(t, e.Node.Key, "/foo") 796 797 s.Update("/foofoo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 798 w, _ = s.Watch("/", true, false, 4) 799 fc.Advance(700 * time.Millisecond) 800 s.DeleteExpiredKeys(fc.Now()) 801 eidx = 5 // We should skip 4 because a TTL update should occur with no watch notification if set `TTLOptionSet.Refresh` to true 802 testutil.AssertEqual(t, w.StartIndex(), eidx-1) 803 e = nbselect(w.EventChan()) 804 testutil.AssertEqual(t, e.EtcdIndex, eidx) 805 testutil.AssertEqual(t, e.Action, "expire") 806 testutil.AssertEqual(t, e.Node.Key, "/foofoo") 807} 808 809// Ensure that the store can watch for key expiration when refreshing with an empty value. 810func TestStoreWatchExpireEmptyRefresh(t *testing.T) { 811 s := newStore() 812 fc := newFakeClock() 813 s.clock = fc 814 815 var eidx uint64 = 1 816 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 817 // Should be no-op 818 fc.Advance(200 * time.Millisecond) 819 s.DeleteExpiredKeys(fc.Now()) 820 821 s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 822 w, _ := s.Watch("/", true, false, 2) 823 fc.Advance(700 * time.Millisecond) 824 s.DeleteExpiredKeys(fc.Now()) 825 eidx = 3 // We should skip 2 because a TTL update should occur with no watch notification if set `TTLOptionSet.Refresh` to true 826 testutil.AssertEqual(t, w.StartIndex(), eidx-1) 827 e := nbselect(w.EventChan()) 828 testutil.AssertEqual(t, e.EtcdIndex, eidx) 829 testutil.AssertEqual(t, e.Action, "expire") 830 testutil.AssertEqual(t, e.Node.Key, "/foo") 831 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 832} 833 834// Update TTL of a key (set TTLOptionSet.Refresh to false) and send notification 835func TestStoreWatchNoRefresh(t *testing.T) { 836 s := newStore() 837 fc := newFakeClock() 838 s.clock = fc 839 840 var eidx uint64 = 1 841 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 842 // Should be no-op 843 fc.Advance(200 * time.Millisecond) 844 s.DeleteExpiredKeys(fc.Now()) 845 846 // Update key's TTL with setting `TTLOptionSet.Refresh` to false will cause an update event 847 s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: false}) 848 w, _ := s.Watch("/", true, false, 2) 849 fc.Advance(700 * time.Millisecond) 850 s.DeleteExpiredKeys(fc.Now()) 851 eidx = 2 852 testutil.AssertEqual(t, w.StartIndex(), eidx) 853 e := nbselect(w.EventChan()) 854 testutil.AssertEqual(t, e.EtcdIndex, eidx) 855 testutil.AssertEqual(t, e.Action, "update") 856 testutil.AssertEqual(t, e.Node.Key, "/foo") 857 testutil.AssertEqual(t, *e.PrevNode.Value, "bar") 858} 859 860// Ensure that the store can update the TTL on a value with refresh. 861func TestStoreRefresh(t *testing.T) { 862 s := newStore() 863 fc := newFakeClock() 864 s.clock = fc 865 866 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 867 s.Create("/bar", true, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 868 _, err := s.Update("/foo", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 869 testutil.AssertNil(t, err) 870 871 _, err = s.Set("/foo", false, "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 872 testutil.AssertNil(t, err) 873 874 _, err = s.Update("/bar", "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 875 testutil.AssertNil(t, err) 876 877 _, err = s.CompareAndSwap("/foo", "bar", 0, "", TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond), Refresh: true}) 878 testutil.AssertNil(t, err) 879} 880 881// Ensure that the store can watch in streaming mode. 882func TestStoreWatchStream(t *testing.T) { 883 s := newStore() 884 var eidx uint64 = 1 885 w, _ := s.Watch("/foo", false, true, 0) 886 // first modification 887 s.Create("/foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 888 e := nbselect(w.EventChan()) 889 testutil.AssertEqual(t, e.EtcdIndex, eidx) 890 testutil.AssertEqual(t, e.Action, "create") 891 testutil.AssertEqual(t, e.Node.Key, "/foo") 892 testutil.AssertEqual(t, *e.Node.Value, "bar") 893 e = nbselect(w.EventChan()) 894 testutil.AssertNil(t, e) 895 // second modification 896 eidx = 2 897 s.Update("/foo", "baz", TTLOptionSet{ExpireTime: Permanent}) 898 e = nbselect(w.EventChan()) 899 testutil.AssertEqual(t, e.EtcdIndex, eidx) 900 testutil.AssertEqual(t, e.Action, "update") 901 testutil.AssertEqual(t, e.Node.Key, "/foo") 902 testutil.AssertEqual(t, *e.Node.Value, "baz") 903 e = nbselect(w.EventChan()) 904 testutil.AssertNil(t, e) 905} 906 907// Ensure that the store can recover from a previously saved state. 908func TestStoreRecover(t *testing.T) { 909 s := newStore() 910 var eidx uint64 = 4 911 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 912 s.Create("/foo/x", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 913 s.Update("/foo/x", "barbar", TTLOptionSet{ExpireTime: Permanent}) 914 s.Create("/foo/y", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 915 b, err := s.Save() 916 testutil.AssertNil(t, err) 917 918 s2 := newStore() 919 s2.Recovery(b) 920 921 e, err := s.Get("/foo/x", false, false) 922 testutil.AssertEqual(t, e.Node.CreatedIndex, uint64(2)) 923 testutil.AssertEqual(t, e.Node.ModifiedIndex, uint64(3)) 924 testutil.AssertEqual(t, e.EtcdIndex, eidx) 925 testutil.AssertNil(t, err) 926 testutil.AssertEqual(t, *e.Node.Value, "barbar") 927 928 e, err = s.Get("/foo/y", false, false) 929 testutil.AssertEqual(t, e.EtcdIndex, eidx) 930 testutil.AssertNil(t, err) 931 testutil.AssertEqual(t, *e.Node.Value, "baz") 932} 933 934// Ensure that the store can recover from a previously saved state that includes an expiring key. 935func TestStoreRecoverWithExpiration(t *testing.T) { 936 s := newStore() 937 s.clock = newFakeClock() 938 939 fc := newFakeClock() 940 941 var eidx uint64 = 4 942 s.Create("/foo", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 943 s.Create("/foo/x", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 944 s.Create("/foo/y", false, "baz", false, TTLOptionSet{ExpireTime: fc.Now().Add(5 * time.Millisecond)}) 945 b, err := s.Save() 946 testutil.AssertNil(t, err) 947 948 time.Sleep(10 * time.Millisecond) 949 950 s2 := newStore() 951 s2.clock = fc 952 953 s2.Recovery(b) 954 955 fc.Advance(600 * time.Millisecond) 956 s.DeleteExpiredKeys(fc.Now()) 957 958 e, err := s.Get("/foo/x", false, false) 959 testutil.AssertNil(t, err) 960 testutil.AssertEqual(t, e.EtcdIndex, eidx) 961 testutil.AssertEqual(t, *e.Node.Value, "bar") 962 963 e, err = s.Get("/foo/y", false, false) 964 testutil.AssertNotNil(t, err) 965 testutil.AssertNil(t, e) 966} 967 968// Ensure that the store can watch for hidden keys as long as it's an exact path match. 969func TestStoreWatchCreateWithHiddenKey(t *testing.T) { 970 s := newStore() 971 var eidx uint64 = 1 972 w, _ := s.Watch("/_foo", false, false, 0) 973 s.Create("/_foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 974 e := nbselect(w.EventChan()) 975 testutil.AssertEqual(t, e.EtcdIndex, eidx) 976 testutil.AssertEqual(t, e.Action, "create") 977 testutil.AssertEqual(t, e.Node.Key, "/_foo") 978 e = nbselect(w.EventChan()) 979 testutil.AssertNil(t, e) 980} 981 982// Ensure that the store doesn't see hidden key creates without an exact path match in recursive mode. 983func TestStoreWatchRecursiveCreateWithHiddenKey(t *testing.T) { 984 s := newStore() 985 w, _ := s.Watch("/foo", true, false, 0) 986 s.Create("/foo/_bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 987 e := nbselect(w.EventChan()) 988 testutil.AssertNil(t, e) 989 w, _ = s.Watch("/foo", true, false, 0) 990 s.Create("/foo/_baz", true, "", false, TTLOptionSet{ExpireTime: Permanent}) 991 e = nbselect(w.EventChan()) 992 testutil.AssertNil(t, e) 993 s.Create("/foo/_baz/quux", false, "quux", false, TTLOptionSet{ExpireTime: Permanent}) 994 e = nbselect(w.EventChan()) 995 testutil.AssertNil(t, e) 996} 997 998// Ensure that the store doesn't see hidden key updates. 999func TestStoreWatchUpdateWithHiddenKey(t *testing.T) { 1000 s := newStore() 1001 s.Create("/_foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 1002 w, _ := s.Watch("/_foo", false, false, 0) 1003 s.Update("/_foo", "baz", TTLOptionSet{ExpireTime: Permanent}) 1004 e := nbselect(w.EventChan()) 1005 testutil.AssertEqual(t, e.Action, "update") 1006 testutil.AssertEqual(t, e.Node.Key, "/_foo") 1007 e = nbselect(w.EventChan()) 1008 testutil.AssertNil(t, e) 1009} 1010 1011// Ensure that the store doesn't see hidden key updates without an exact path match in recursive mode. 1012func TestStoreWatchRecursiveUpdateWithHiddenKey(t *testing.T) { 1013 s := newStore() 1014 s.Create("/foo/_bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 1015 w, _ := s.Watch("/foo", true, false, 0) 1016 s.Update("/foo/_bar", "baz", TTLOptionSet{ExpireTime: Permanent}) 1017 e := nbselect(w.EventChan()) 1018 testutil.AssertNil(t, e) 1019} 1020 1021// Ensure that the store can watch for key deletions. 1022func TestStoreWatchDeleteWithHiddenKey(t *testing.T) { 1023 s := newStore() 1024 var eidx uint64 = 2 1025 s.Create("/_foo", false, "bar", false, TTLOptionSet{ExpireTime: Permanent}) 1026 w, _ := s.Watch("/_foo", false, false, 0) 1027 s.Delete("/_foo", false, false) 1028 e := nbselect(w.EventChan()) 1029 testutil.AssertEqual(t, e.EtcdIndex, eidx) 1030 testutil.AssertEqual(t, e.Action, "delete") 1031 testutil.AssertEqual(t, e.Node.Key, "/_foo") 1032 e = nbselect(w.EventChan()) 1033 testutil.AssertNil(t, e) 1034} 1035 1036// Ensure that the store doesn't see hidden key deletes without an exact path match in recursive mode. 1037func TestStoreWatchRecursiveDeleteWithHiddenKey(t *testing.T) { 1038 s := newStore() 1039 s.Create("/foo/_bar", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 1040 w, _ := s.Watch("/foo", true, false, 0) 1041 s.Delete("/foo/_bar", false, false) 1042 e := nbselect(w.EventChan()) 1043 testutil.AssertNil(t, e) 1044} 1045 1046// Ensure that the store doesn't see expirations of hidden keys. 1047func TestStoreWatchExpireWithHiddenKey(t *testing.T) { 1048 s := newStore() 1049 fc := newFakeClock() 1050 s.clock = fc 1051 1052 s.Create("/_foo", false, "bar", false, TTLOptionSet{ExpireTime: fc.Now().Add(500 * time.Millisecond)}) 1053 s.Create("/foofoo", false, "barbarbar", false, TTLOptionSet{ExpireTime: fc.Now().Add(1000 * time.Millisecond)}) 1054 1055 w, _ := s.Watch("/", true, false, 0) 1056 c := w.EventChan() 1057 e := nbselect(c) 1058 testutil.AssertNil(t, e) 1059 fc.Advance(600 * time.Millisecond) 1060 s.DeleteExpiredKeys(fc.Now()) 1061 e = nbselect(c) 1062 testutil.AssertNil(t, e) 1063 fc.Advance(600 * time.Millisecond) 1064 s.DeleteExpiredKeys(fc.Now()) 1065 e = nbselect(c) 1066 testutil.AssertEqual(t, e.Action, "expire") 1067 testutil.AssertEqual(t, e.Node.Key, "/foofoo") 1068} 1069 1070// Ensure that the store does see hidden key creates if watching deeper than a hidden key in recursive mode. 1071func TestStoreWatchRecursiveCreateDeeperThanHiddenKey(t *testing.T) { 1072 s := newStore() 1073 var eidx uint64 = 1 1074 w, _ := s.Watch("/_foo/bar", true, false, 0) 1075 s.Create("/_foo/bar/baz", false, "baz", false, TTLOptionSet{ExpireTime: Permanent}) 1076 1077 e := nbselect(w.EventChan()) 1078 testutil.AssertNotNil(t, e) 1079 testutil.AssertEqual(t, e.EtcdIndex, eidx) 1080 testutil.AssertEqual(t, e.Action, "create") 1081 testutil.AssertEqual(t, e.Node.Key, "/_foo/bar/baz") 1082} 1083 1084// Ensure that slow consumers are handled properly. 1085// 1086// Since Watcher.EventChan() has a buffer of size 100 we can only queue 100 1087// event per watcher. If the consumer cannot consume the event on time and 1088// another event arrives, the channel is closed and event is discarded. 1089// This test ensures that after closing the channel, the store can continue 1090// to operate correctly. 1091func TestStoreWatchSlowConsumer(t *testing.T) { 1092 s := newStore() 1093 s.Watch("/foo", true, true, 0) // stream must be true 1094 // Fill watch channel with 100 events 1095 for i := 1; i <= 100; i++ { 1096 s.Set("/foo", false, string(i), TTLOptionSet{ExpireTime: Permanent}) // ok 1097 } 1098 testutil.AssertEqual(t, s.WatcherHub.count, int64(1)) 1099 s.Set("/foo", false, "101", TTLOptionSet{ExpireTime: Permanent}) // ok 1100 // remove watcher 1101 testutil.AssertEqual(t, s.WatcherHub.count, int64(0)) 1102 s.Set("/foo", false, "102", TTLOptionSet{ExpireTime: Permanent}) // must not panic 1103} 1104 1105// Performs a non-blocking select on an event channel. 1106func nbselect(c <-chan *Event) *Event { 1107 select { 1108 case e := <-c: 1109 return e 1110 default: 1111 return nil 1112 } 1113} 1114 1115// newFakeClock creates a new FakeClock that has been advanced to at least minExpireTime 1116func newFakeClock() clockwork.FakeClock { 1117 fc := clockwork.NewFakeClock() 1118 for minExpireTime.After(fc.Now()) { 1119 fc.Advance((0x1 << 62) * time.Nanosecond) 1120 } 1121 return fc 1122} 1123