1package api 2 3import ( 4 "context" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10) 11 12func TestAPI_SessionCreateDestroy(t *testing.T) { 13 t.Parallel() 14 c, s := makeClient(t) 15 defer s.Stop() 16 17 s.WaitForSerfCheck(t) 18 19 session := c.Session() 20 21 id, meta, err := session.Create(nil, nil) 22 if err != nil { 23 t.Fatalf("err: %v", err) 24 } 25 26 if meta.RequestTime == 0 { 27 t.Fatalf("bad: %v", meta) 28 } 29 30 if id == "" { 31 t.Fatalf("invalid: %v", id) 32 } 33 34 meta, err = session.Destroy(id, nil) 35 if err != nil { 36 t.Fatalf("err: %v", err) 37 } 38 39 if meta.RequestTime == 0 { 40 t.Fatalf("bad: %v", meta) 41 } 42} 43 44func TestAPI_SessionCreateRenewDestroy(t *testing.T) { 45 t.Parallel() 46 c, s := makeClient(t) 47 defer s.Stop() 48 49 s.WaitForSerfCheck(t) 50 51 session := c.Session() 52 53 se := &SessionEntry{ 54 TTL: "10s", 55 } 56 57 id, meta, err := session.Create(se, nil) 58 if err != nil { 59 t.Fatalf("err: %v", err) 60 } 61 defer session.Destroy(id, nil) 62 63 if meta.RequestTime == 0 { 64 t.Fatalf("bad: %v", meta) 65 } 66 67 if id == "" { 68 t.Fatalf("invalid: %v", id) 69 } 70 71 if meta.RequestTime == 0 { 72 t.Fatalf("bad: %v", meta) 73 } 74 75 renew, meta, err := session.Renew(id, nil) 76 77 if err != nil { 78 t.Fatalf("err: %v", err) 79 } 80 if meta.RequestTime == 0 { 81 t.Fatalf("bad: %v", meta) 82 } 83 84 if renew == nil { 85 t.Fatalf("should get session") 86 } 87 88 if renew.ID != id { 89 t.Fatalf("should have matching id") 90 } 91 92 if renew.TTL != "10s" { 93 t.Fatalf("should get session with TTL") 94 } 95} 96 97func TestAPI_SessionCreateRenewDestroyRenew(t *testing.T) { 98 t.Parallel() 99 c, s := makeClient(t) 100 defer s.Stop() 101 102 s.WaitForSerfCheck(t) 103 104 session := c.Session() 105 106 entry := &SessionEntry{ 107 Behavior: SessionBehaviorDelete, 108 TTL: "500s", // disable ttl 109 } 110 111 id, meta, err := session.Create(entry, nil) 112 if err != nil { 113 t.Fatalf("err: %v", err) 114 } 115 116 if meta.RequestTime == 0 { 117 t.Fatalf("bad: %v", meta) 118 } 119 120 if id == "" { 121 t.Fatalf("invalid: %v", id) 122 } 123 124 // Extend right after create. Everything should be fine. 125 entry, _, err = session.Renew(id, nil) 126 if err != nil { 127 t.Fatalf("err: %v", err) 128 } 129 if entry == nil { 130 t.Fatal("session unexpectedly vanished") 131 } 132 133 // Simulate TTL loss by manually destroying the session. 134 meta, err = session.Destroy(id, nil) 135 if err != nil { 136 t.Fatalf("err: %v", err) 137 } 138 139 if meta.RequestTime == 0 { 140 t.Fatalf("bad: %v", meta) 141 } 142 143 // Extend right after delete. The 404 should proxy as a nil. 144 entry, _, err = session.Renew(id, nil) 145 if err != nil { 146 t.Fatalf("err: %v", err) 147 } 148 if entry != nil { 149 t.Fatal("session still exists") 150 } 151} 152 153func TestAPI_SessionCreateDestroyRenewPeriodic(t *testing.T) { 154 t.Parallel() 155 c, s := makeClient(t) 156 defer s.Stop() 157 158 s.WaitForSerfCheck(t) 159 160 session := c.Session() 161 162 entry := &SessionEntry{ 163 Behavior: SessionBehaviorDelete, 164 TTL: "500s", // disable ttl 165 } 166 167 id, meta, err := session.Create(entry, nil) 168 if err != nil { 169 t.Fatalf("err: %v", err) 170 } 171 172 if meta.RequestTime == 0 { 173 t.Fatalf("bad: %v", meta) 174 } 175 176 if id == "" { 177 t.Fatalf("invalid: %v", id) 178 } 179 180 // This only tests Create/Destroy/RenewPeriodic to avoid the more 181 // difficult case of testing all of the timing code. 182 183 // Simulate TTL loss by manually destroying the session. 184 meta, err = session.Destroy(id, nil) 185 if err != nil { 186 t.Fatalf("err: %v", err) 187 } 188 189 if meta.RequestTime == 0 { 190 t.Fatalf("bad: %v", meta) 191 } 192 193 // Extend right after delete. The 404 should terminate the loop quickly and return ErrSessionExpired. 194 errCh := make(chan error, 1) 195 doneCh := make(chan struct{}) 196 go func() { errCh <- session.RenewPeriodic("1s", id, nil, doneCh) }() 197 defer close(doneCh) 198 199 select { 200 case <-time.After(1 * time.Second): 201 t.Fatal("timedout: missing session did not terminate renewal loop") 202 case err = <-errCh: 203 if err != ErrSessionExpired { 204 t.Fatalf("err: %v", err) 205 } 206 } 207} 208 209func TestAPI_SessionRenewPeriodic_Cancel(t *testing.T) { 210 t.Parallel() 211 c, s := makeClient(t) 212 defer s.Stop() 213 214 s.WaitForSerfCheck(t) 215 216 session := c.Session() 217 entry := &SessionEntry{ 218 Behavior: SessionBehaviorDelete, 219 TTL: "500s", // disable ttl 220 } 221 222 t.Run("done channel", func(t *testing.T) { 223 id, _, err := session.Create(entry, nil) 224 if err != nil { 225 t.Fatalf("err: %v", err) 226 } 227 228 errCh := make(chan error, 1) 229 doneCh := make(chan struct{}) 230 go func() { errCh <- session.RenewPeriodic("1s", id, nil, doneCh) }() 231 232 close(doneCh) 233 234 select { 235 case <-time.After(1 * time.Second): 236 t.Fatal("renewal loop didn't terminate") 237 case err = <-errCh: 238 if err != nil { 239 t.Fatalf("err: %v", err) 240 } 241 } 242 243 sess, _, err := session.Info(id, nil) 244 if err != nil { 245 t.Fatalf("err: %v", err) 246 } 247 if sess != nil { 248 t.Fatalf("session was not expired") 249 } 250 }) 251 252 t.Run("context", func(t *testing.T) { 253 id, _, err := session.Create(entry, nil) 254 if err != nil { 255 t.Fatalf("err: %v", err) 256 } 257 258 ctx, cancel := context.WithCancel(context.Background()) 259 wo := new(WriteOptions).WithContext(ctx) 260 261 errCh := make(chan error, 1) 262 go func() { errCh <- session.RenewPeriodic("1s", id, wo, nil) }() 263 264 cancel() 265 266 select { 267 case <-time.After(1 * time.Second): 268 t.Fatal("renewal loop didn't terminate") 269 case err = <-errCh: 270 if err == nil || !strings.Contains(err.Error(), "context canceled") { 271 t.Fatalf("err: %v", err) 272 } 273 } 274 275 // See comment in session.go for why the session isn't removed 276 // in this case. 277 sess, _, err := session.Info(id, nil) 278 if err != nil { 279 t.Fatalf("err: %v", err) 280 } 281 if sess == nil { 282 t.Fatalf("session should not be expired") 283 } 284 }) 285} 286 287func TestAPI_SessionInfo(t *testing.T) { 288 t.Parallel() 289 c, s := makeClient(t) 290 defer s.Stop() 291 292 s.WaitForSerfCheck(t) 293 294 session := c.Session() 295 296 id, _, err := session.Create(nil, nil) 297 if err != nil { 298 t.Fatalf("err: %v", err) 299 } 300 defer session.Destroy(id, nil) 301 302 info, qm, err := session.Info(id, nil) 303 if err != nil { 304 t.Fatalf("err: %v", err) 305 } 306 if qm.LastIndex == 0 { 307 t.Fatalf("bad: %v", qm) 308 } 309 if !qm.KnownLeader { 310 t.Fatalf("bad: %v", qm) 311 } 312 313 if info.CreateIndex == 0 { 314 t.Fatalf("bad: %v", info) 315 } 316 info.CreateIndex = 0 317 318 want := &SessionEntry{ 319 ID: id, 320 Node: s.Config.NodeName, 321 NodeChecks: []string{"serfHealth"}, 322 LockDelay: 15 * time.Second, 323 Behavior: SessionBehaviorRelease, 324 } 325 if info.ID != want.ID { 326 t.Fatalf("bad ID: %s", info.ID) 327 } 328 if info.Node != want.Node { 329 t.Fatalf("bad Node: %s", info.Node) 330 } 331 if info.LockDelay != want.LockDelay { 332 t.Fatalf("bad LockDelay: %d", info.LockDelay) 333 } 334 if info.Behavior != want.Behavior { 335 t.Fatalf("bad Behavior: %s", info.Behavior) 336 } 337 if len(info.NodeChecks) != len(want.NodeChecks) { 338 t.Fatalf("expected %d nodechecks, got %d", len(want.NodeChecks), len(info.NodeChecks)) 339 } 340 if info.NodeChecks[0] != want.NodeChecks[0] { 341 t.Fatalf("expected nodecheck %s, got %s", want.NodeChecks, info.NodeChecks) 342 } 343} 344 345func TestAPI_SessionInfo_NoChecks(t *testing.T) { 346 t.Parallel() 347 c, s := makeClient(t) 348 defer s.Stop() 349 350 s.WaitForSerfCheck(t) 351 352 session := c.Session() 353 354 id, _, err := session.CreateNoChecks(nil, nil) 355 if err != nil { 356 t.Fatalf("err: %v", err) 357 } 358 defer session.Destroy(id, nil) 359 360 info, qm, err := session.Info(id, nil) 361 if err != nil { 362 t.Fatalf("err: %v", err) 363 } 364 365 if qm.LastIndex == 0 { 366 t.Fatalf("bad: %v", qm) 367 } 368 if !qm.KnownLeader { 369 t.Fatalf("bad: %v", qm) 370 } 371 372 if info.CreateIndex == 0 { 373 t.Fatalf("bad: %v", info) 374 } 375 info.CreateIndex = 0 376 377 want := &SessionEntry{ 378 ID: id, 379 Node: s.Config.NodeName, 380 NodeChecks: []string{}, 381 LockDelay: 15 * time.Second, 382 Behavior: SessionBehaviorRelease, 383 } 384 if info.ID != want.ID { 385 t.Fatalf("bad ID: %s", info.ID) 386 } 387 if info.Node != want.Node { 388 t.Fatalf("bad Node: %s", info.Node) 389 } 390 if info.LockDelay != want.LockDelay { 391 t.Fatalf("bad LockDelay: %d", info.LockDelay) 392 } 393 if info.Behavior != want.Behavior { 394 t.Fatalf("bad Behavior: %s", info.Behavior) 395 } 396 assert.Equal(t, want.Checks, info.Checks) 397 assert.Equal(t, want.NodeChecks, info.NodeChecks) 398} 399 400func TestAPI_SessionNode(t *testing.T) { 401 t.Parallel() 402 c, s := makeClient(t) 403 defer s.Stop() 404 405 s.WaitForSerfCheck(t) 406 407 session := c.Session() 408 409 id, _, err := session.Create(nil, nil) 410 if err != nil { 411 t.Fatalf("err: %v", err) 412 } 413 defer session.Destroy(id, nil) 414 415 info, _, err := session.Info(id, nil) 416 if err != nil { 417 t.Fatalf("err: %v", err) 418 } 419 420 sessions, qm, err := session.Node(info.Node, nil) 421 if err != nil { 422 t.Fatalf("err: %v", err) 423 } 424 425 if len(sessions) != 1 { 426 t.Fatalf("bad: %v", sessions) 427 } 428 429 if qm.LastIndex == 0 { 430 t.Fatalf("bad: %v", qm) 431 } 432 if !qm.KnownLeader { 433 t.Fatalf("bad: %v", qm) 434 } 435} 436 437func TestAPI_SessionList(t *testing.T) { 438 t.Parallel() 439 c, s := makeClient(t) 440 defer s.Stop() 441 442 s.WaitForSerfCheck(t) 443 444 session := c.Session() 445 446 id, _, err := session.Create(nil, nil) 447 if err != nil { 448 t.Fatalf("err: %v", err) 449 } 450 defer session.Destroy(id, nil) 451 452 sessions, qm, err := session.List(nil) 453 if err != nil { 454 t.Fatalf("err: %v", err) 455 } 456 457 if len(sessions) != 1 { 458 t.Fatalf("bad: %v", sessions) 459 } 460 461 if qm.LastIndex == 0 { 462 t.Fatalf("bad: %v", qm) 463 } 464 if !qm.KnownLeader { 465 t.Fatalf("bad: %v", qm) 466 } 467} 468 469func TestAPI_SessionNodeChecks(t *testing.T) { 470 t.Parallel() 471 c, s := makeClient(t) 472 defer s.Stop() 473 474 s.WaitForSerfCheck(t) 475 476 // Node check that doesn't exist should yield error on creation 477 se := SessionEntry{ 478 NodeChecks: []string{"dne"}, 479 } 480 session := c.Session() 481 482 _, _, err := session.Create(&se, nil) 483 if err == nil { 484 t.Fatalf("should have failed") 485 } 486 487 // Empty node check should lead to serf check 488 se.NodeChecks = []string{} 489 id, _, err := session.Create(&se, nil) 490 if err != nil { 491 t.Fatalf("err: %v", err) 492 } 493 defer session.Destroy(id, nil) 494 495 info, qm, err := session.Info(id, nil) 496 if err != nil { 497 t.Fatalf("err: %v", err) 498 } 499 if qm.LastIndex == 0 { 500 t.Fatalf("bad: %v", qm) 501 } 502 if !qm.KnownLeader { 503 t.Fatalf("bad: %v", qm) 504 } 505 if info.CreateIndex == 0 { 506 t.Fatalf("bad: %v", info) 507 } 508 info.CreateIndex = 0 509 510 want := &SessionEntry{ 511 ID: id, 512 Node: s.Config.NodeName, 513 NodeChecks: []string{"serfHealth"}, 514 LockDelay: 15 * time.Second, 515 Behavior: SessionBehaviorRelease, 516 } 517 want.Namespace = info.Namespace 518 assert.Equal(t, want, info) 519 520 // Register a new node with a non-serf check 521 cr := CatalogRegistration{ 522 Datacenter: "dc1", 523 Node: "foo", 524 ID: "e0155642-135d-4739-9853-a1ee6c9f945b", 525 Address: "127.0.0.2", 526 Checks: HealthChecks{ 527 &HealthCheck{ 528 Node: "foo", 529 CheckID: "foo:alive", 530 Name: "foo-liveness", 531 Status: HealthPassing, 532 Notes: "foo is alive and well", 533 }, 534 }, 535 } 536 catalog := c.Catalog() 537 if _, err := catalog.Register(&cr, nil); err != nil { 538 t.Fatalf("err: %v", err) 539 } 540 541 // If a custom node check is provided, it should overwrite serfHealth default 542 se.Node = "foo" 543 se.NodeChecks = []string{"foo:alive"} 544 545 id, _, err = session.Create(&se, nil) 546 if err != nil { 547 t.Fatalf("err: %v", err) 548 } 549 defer session.Destroy(id, nil) 550 551 info, qm, err = session.Info(id, nil) 552 if err != nil { 553 t.Fatalf("err: %v", err) 554 } 555 if qm.LastIndex == 0 { 556 t.Fatalf("bad: %v", qm) 557 } 558 if !qm.KnownLeader { 559 t.Fatalf("bad: %v", qm) 560 } 561 if info.CreateIndex == 0 { 562 t.Fatalf("bad: %v", info) 563 } 564 info.CreateIndex = 0 565 566 want = &SessionEntry{ 567 ID: id, 568 Node: "foo", 569 NodeChecks: []string{"foo:alive"}, 570 LockDelay: 15 * time.Second, 571 Behavior: SessionBehaviorRelease, 572 } 573 want.Namespace = info.Namespace 574 assert.Equal(t, want, info) 575} 576 577func TestAPI_SessionServiceChecks(t *testing.T) { 578 t.Parallel() 579 c, s := makeClient(t) 580 defer s.Stop() 581 582 s.WaitForSerfCheck(t) 583 584 // Node check that doesn't exist should yield error on creation 585 se := SessionEntry{ 586 ServiceChecks: []ServiceCheck{ 587 {"dne", ""}, 588 }, 589 } 590 session := c.Session() 591 592 _, _, err := session.Create(&se, nil) 593 if err == nil { 594 t.Fatalf("should have failed") 595 } 596 597 // Register a new service with a check 598 cr := CatalogRegistration{ 599 Datacenter: "dc1", 600 Node: s.Config.NodeName, 601 SkipNodeUpdate: true, 602 Service: &AgentService{ 603 Kind: ServiceKindTypical, 604 ID: "redisV2", 605 Service: "redis", 606 Port: 1235, 607 Address: "198.18.1.2", 608 }, 609 Checks: HealthChecks{ 610 &HealthCheck{ 611 Node: s.Config.NodeName, 612 CheckID: "redis:alive", 613 Status: HealthPassing, 614 ServiceID: "redisV2", 615 }, 616 }, 617 } 618 catalog := c.Catalog() 619 if _, err := catalog.Register(&cr, nil); err != nil { 620 t.Fatalf("err: %v", err) 621 } 622 623 // If a custom check is provided, it should be present in session info 624 se.ServiceChecks = []ServiceCheck{ 625 {"redis:alive", ""}, 626 } 627 628 id, _, err := session.Create(&se, nil) 629 if err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 defer session.Destroy(id, nil) 633 634 info, qm, err := session.Info(id, nil) 635 if err != nil { 636 t.Fatalf("err: %v", err) 637 } 638 if qm.LastIndex == 0 { 639 t.Fatalf("bad: %v", qm) 640 } 641 if !qm.KnownLeader { 642 t.Fatalf("bad: %v", qm) 643 } 644 if info.CreateIndex == 0 { 645 t.Fatalf("bad: %v", info) 646 } 647 info.CreateIndex = 0 648 649 want := &SessionEntry{ 650 ID: id, 651 Node: s.Config.NodeName, 652 ServiceChecks: []ServiceCheck{{"redis:alive", ""}}, 653 NodeChecks: []string{"serfHealth"}, 654 LockDelay: 15 * time.Second, 655 Behavior: SessionBehaviorRelease, 656 } 657 want.Namespace = info.Namespace 658 assert.Equal(t, want, info) 659} 660