github.com/openshift-online/ocm-sdk-go@v0.1.473/leadership/flag_test.go (about) 1 /* 2 Copyright (c) 2021 Red Hat, Inc. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package leadership 18 19 import ( 20 "context" 21 "database/sql" 22 "fmt" 23 "sync/atomic" 24 "time" 25 26 . "github.com/onsi/ginkgo/v2/dsl/core" // nolint 27 . "github.com/onsi/gomega" // nolint 28 . "github.com/openshift-online/ocm-sdk-go/testing" // nolint 29 ) 30 31 var _ = Describe("Flag behaviour", func() { 32 var ctx context.Context 33 var dbObject *Database 34 var dbHandle *sql.DB 35 36 var CreateTable = func() { 37 _, err := dbHandle.Exec(` 38 create table leadership_flags ( 39 name text not null primary key, 40 holder text not null, 41 version bigint not null, 42 timestamp timestamp with time zone not null 43 ) 44 `) 45 Expect(err).ToNot(HaveOccurred()) 46 } 47 48 BeforeEach(func() { 49 // Create a context: 50 ctx = context.Background() 51 52 // Create a database: 53 dbObject = dbServer.MakeDatabase() 54 dbHandle = dbObject.MakeHandle() 55 }) 56 57 AfterEach(func() { 58 dbObject.Close() 59 }) 60 61 It("Can't be created without a logger", func() { 62 _, err := NewFlag(). 63 Handle(dbHandle). 64 Name("my_flag"). 65 Process("my_process"). 66 Build(ctx) 67 Expect(err).To(HaveOccurred()) 68 message := err.Error() 69 Expect(message).To(ContainSubstring("logger")) 70 Expect(message).To(ContainSubstring("mandatory")) 71 }) 72 73 It("Can't be created without a database handle", func() { 74 _, err := NewFlag(). 75 Logger(logger). 76 Name("my_flag"). 77 Process("my_process"). 78 Build(ctx) 79 Expect(err).To(HaveOccurred()) 80 message := err.Error() 81 Expect(message).To(ContainSubstring("database")) 82 Expect(message).To(ContainSubstring("handle")) 83 Expect(message).To(ContainSubstring("mandatory")) 84 }) 85 86 It("Can't be created without a name", func() { 87 _, err := NewFlag(). 88 Logger(logger). 89 Handle(dbHandle). 90 Process("my_process"). 91 Build(ctx) 92 Expect(err).To(HaveOccurred()) 93 message := err.Error() 94 Expect(message).To(ContainSubstring("name")) 95 Expect(message).To(ContainSubstring("mandatory")) 96 }) 97 98 It("Can't be created without a process name", func() { 99 _, err := NewFlag(). 100 Logger(logger). 101 Handle(dbHandle). 102 Name("my_flag"). 103 Build(ctx) 104 Expect(err).To(HaveOccurred()) 105 message := err.Error() 106 Expect(message).To(ContainSubstring("process")) 107 Expect(message).To(ContainSubstring("mandatory")) 108 }) 109 110 It("Creates the table if it doesn't exist", func() { 111 // Create the flag: 112 flag, err := NewFlag(). 113 Logger(logger). 114 Handle(dbHandle). 115 Name("my_flag"). 116 Process("my_process"). 117 Build(ctx) 118 Expect(err).ToNot(HaveOccurred()) 119 defer func() { 120 err = flag.Close() 121 Expect(err).ToNot(HaveOccurred()) 122 }() 123 124 // Check that the table exists: 125 rows, err := dbHandle.Query(` 126 select 127 name, 128 holder, 129 version, 130 timestamp 131 from 132 leadership_flags 133 `) 134 Expect(err).ToNot(HaveOccurred()) 135 err = rows.Close() 136 Expect(err).ToNot(HaveOccurred()) 137 }) 138 139 It("Can be created if the table already exists", func() { 140 // Create the database table: 141 CreateTable() 142 143 // Create the flag object: 144 flag, err := NewFlag(). 145 Logger(logger). 146 Handle(dbHandle). 147 Name("my_flag"). 148 Process("my_process"). 149 Build(ctx) 150 Expect(err).ToNot(HaveOccurred()) 151 defer func() { 152 err = flag.Close() 153 Expect(err).ToNot(HaveOccurred()) 154 }() 155 }) 156 157 When("Doesn't exist", func() { 158 var flag *Flag 159 160 BeforeEach(func() { 161 var err error 162 163 // Create the flag object: 164 flag, err = NewFlag(). 165 Logger(logger). 166 Handle(dbHandle). 167 Name("my_flag"). 168 Process("my_process"). 169 Interval(200 * time.Millisecond). 170 Build(ctx) 171 Expect(err).ToNot(HaveOccurred()) 172 }) 173 174 AfterEach(func() { 175 err := flag.Close() 176 Expect(err).ToNot(HaveOccurred()) 177 }) 178 179 It("It is quickly raised ", func() { 180 time.Sleep(40 * time.Millisecond) 181 Expect(flag.Raised()).To(BeTrue()) 182 }) 183 }) 184 185 When("Is held by another process and not expired", func() { 186 var flag *Flag 187 188 BeforeEach(func() { 189 var err error 190 191 // Create the database table: 192 CreateTable() 193 194 // Create the database row so that the flag is already held by another 195 // process that will eventually fail to update it: 196 _, err = dbHandle.Exec(` 197 insert into leadership_flags ( 198 name, 199 holder, 200 version, 201 timestamp 202 ) values ( 203 'my_flag', 204 'your_process', 205 123, 206 now() 207 ) 208 `) 209 Expect(err).ToNot(HaveOccurred()) 210 211 // Create the object: 212 flag, err = NewFlag(). 213 Logger(logger). 214 Handle(dbHandle). 215 Name("my_flag"). 216 Process("my_process"). 217 Interval(200 * time.Millisecond). 218 Jitter(0). 219 Build(ctx) 220 Expect(err).ToNot(HaveOccurred()) 221 }) 222 223 AfterEach(func() { 224 err := flag.Close() 225 Expect(err).ToNot(HaveOccurred()) 226 }) 227 228 It("It isn't quickly raised", func() { 229 time.Sleep(40 * time.Millisecond) 230 Expect(flag.Raised()).To(BeFalse()) 231 }) 232 233 It("It isn't raised while the previous holder can still renew", func() { 234 time.Sleep(100 * time.Millisecond) 235 Expect(flag.Raised()).To(BeFalse()) 236 }) 237 238 It("It is raised when the previous holder fails to renew", func() { 239 time.Sleep(400 * time.Millisecond) 240 Expect(flag.Raised()).To(BeTrue()) 241 }) 242 }) 243 244 When("Is held by another process but already expired", func() { 245 var flag *Flag 246 247 BeforeEach(func() { 248 var err error 249 250 // Create the database table: 251 CreateTable() 252 253 // Create the database row so that the flag is already held by another 254 // process that already failed to renew it: 255 _, err = dbHandle.Exec(` 256 insert into leadership_flags ( 257 name, 258 holder, 259 version, 260 timestamp 261 ) values ( 262 'my_flag', 263 'your_process', 264 123, 265 now() - interval '1 second' 266 ) 267 `) 268 Expect(err).ToNot(HaveOccurred()) 269 270 // Create the object: 271 flag, err = NewFlag(). 272 Logger(logger). 273 Handle(dbHandle). 274 Name("my_flag"). 275 Process("my_process"). 276 Interval(200 * time.Millisecond). 277 Jitter(0). 278 Build(ctx) 279 Expect(err).ToNot(HaveOccurred()) 280 }) 281 282 AfterEach(func() { 283 err := flag.Close() 284 Expect(err).ToNot(HaveOccurred()) 285 }) 286 287 It("It is quickly raised", func() { 288 time.Sleep(40 * time.Millisecond) 289 Expect(flag.Raised()).To(BeTrue()) 290 }) 291 }) 292 293 When("Is held by this process and not expired", func() { 294 var flag *Flag 295 296 BeforeEach(func() { 297 var err error 298 299 // Create the database table: 300 CreateTable() 301 302 // Create the database row so that the flag is already held by this process: 303 _, err = dbHandle.Exec(` 304 insert into leadership_flags ( 305 name, 306 holder, 307 version, 308 timestamp 309 ) values ( 310 'my_flag', 311 'my_process', 312 123, 313 now() 314 ) 315 `) 316 Expect(err).ToNot(HaveOccurred()) 317 318 // Create the object: 319 flag, err = NewFlag(). 320 Logger(logger). 321 Handle(dbHandle). 322 Name("my_flag"). 323 Process("my_process"). 324 Interval(200 * time.Millisecond). 325 Jitter(0). 326 Build(ctx) 327 Expect(err).ToNot(HaveOccurred()) 328 }) 329 330 AfterEach(func() { 331 err := flag.Close() 332 Expect(err).ToNot(HaveOccurred()) 333 }) 334 335 It("It is quickly raised", func() { 336 time.Sleep(40 * time.Millisecond) 337 Expect(flag.Raised()).To(BeTrue()) 338 }) 339 }) 340 341 When("Is held by this process and already expired", func() { 342 var flag *Flag 343 344 BeforeEach(func() { 345 var err error 346 347 // Create the database table: 348 CreateTable() 349 350 // Create the database row so that the flag is held by this process but 351 // expired: 352 _, err = dbHandle.Exec(` 353 insert into leadership_flags ( 354 name, 355 holder, 356 version, 357 timestamp 358 ) values ( 359 'my_flag', 360 'my_process', 361 123, 362 now() - interval '1 second' 363 ) 364 `) 365 Expect(err).ToNot(HaveOccurred()) 366 367 // Create the object: 368 flag, err = NewFlag(). 369 Logger(logger). 370 Handle(dbHandle). 371 Name("my_flag"). 372 Process("my_process"). 373 Interval(200 * time.Millisecond). 374 Jitter(0). 375 Build(ctx) 376 Expect(err).ToNot(HaveOccurred()) 377 }) 378 379 AfterEach(func() { 380 err := flag.Close() 381 Expect(err).ToNot(HaveOccurred()) 382 }) 383 384 It("It isn't quickly raised", func() { 385 time.Sleep(40 * time.Millisecond) 386 Expect(flag.Raised()).To(BeTrue()) 387 }) 388 }) 389 390 When("Current holder closes the flag", func() { 391 It("Is raised by another process", func() { 392 var err error 393 394 // Create the first process: 395 first, err := NewFlag(). 396 Logger(logger). 397 Handle(dbHandle). 398 Name("my_flag"). 399 Process("first_process"). 400 Interval(200 * time.Millisecond). 401 Jitter(0). 402 Build(ctx) 403 Expect(err).ToNot(HaveOccurred()) 404 405 // Give the first process some time to get hold of the flag and then check 406 // that it did: 407 time.Sleep(200 * time.Millisecond) 408 Expect(first.Raised()).To(BeTrue()) 409 410 // Create the second process: 411 second, err := NewFlag(). 412 Logger(logger). 413 Handle(dbHandle). 414 Name("my_flag"). 415 Process("second_process"). 416 Interval(200 * time.Millisecond). 417 Jitter(0). 418 Build(ctx) 419 Expect(err).ToNot(HaveOccurred()) 420 defer func() { 421 err = second.Close() 422 Expect(err).ToNot(HaveOccurred()) 423 }() 424 425 // Give the second process some time to try to get hold of the flag and 426 // check that it didn't: 427 time.Sleep(200 * time.Millisecond) 428 Expect(second.Raised()).To(BeFalse()) 429 430 // Close the first process so that it will fail to renew the flag: 431 err = first.Close() 432 Expect(err).ToNot(HaveOccurred()) 433 434 // Allow time for the second process to get hold of the flag and check that 435 // it did: 436 time.Sleep(400 * time.Millisecond) 437 Expect(second.Raised()).To(BeTrue()) 438 }) 439 }) 440 441 When("Current holder loses database connection", func() { 442 It("Is raised by another process", func() { 443 var err error 444 445 // Create the first process, but using a separate database handle, so that 446 // we can close it without affecting the second process: 447 altHandle := dbObject.MakeHandle() 448 first, err := NewFlag(). 449 Logger(logger). 450 Handle(altHandle). 451 Name("my_flag"). 452 Process("first_process"). 453 Interval(200 * time.Millisecond). 454 Jitter(0). 455 Build(ctx) 456 Expect(err).ToNot(HaveOccurred()) 457 defer func() { 458 err = first.Close() 459 Expect(err).ToNot(HaveOccurred()) 460 }() 461 462 // Give the first process some time to get hold of the flag and then check 463 // that it did: 464 time.Sleep(200 * time.Millisecond) 465 Expect(first.Raised()).To(BeTrue()) 466 467 // Create the second process: 468 second, err := NewFlag(). 469 Logger(logger). 470 Handle(dbHandle). 471 Name("my_flag"). 472 Process("second_process"). 473 Interval(200 * time.Millisecond). 474 Jitter(0). 475 Build(ctx) 476 Expect(err).ToNot(HaveOccurred()) 477 defer func() { 478 err = second.Close() 479 Expect(err).ToNot(HaveOccurred()) 480 }() 481 482 // Give the second process some time to try to get hold of the flag and then 483 // check that it didn't: 484 time.Sleep(200 * time.Millisecond) 485 Expect(second.Raised()).To(BeFalse()) 486 487 // Close the database connection of the first process: 488 err = altHandle.Close() 489 Expect(err).ToNot(HaveOccurred()) 490 491 // Allow time for the second process to get hold of the flag and then check 492 // that it did: 493 time.Sleep(400 * time.Millisecond) 494 Expect(second.Raised()).To(BeTrue()) 495 }) 496 }) 497 498 When("Stolen", func() { 499 It("Is lowers", func() { 500 var err error 501 502 // Create the flag: 503 flag, err := NewFlag(). 504 Logger(logger). 505 Handle(dbHandle). 506 Name("my_flag"). 507 Process("my_process"). 508 Interval(200 * time.Millisecond). 509 Jitter(0). 510 Build(ctx) 511 Expect(err).ToNot(HaveOccurred()) 512 defer func() { 513 err = flag.Close() 514 Expect(err).ToNot(HaveOccurred()) 515 }() 516 517 // Give the process some time to get hold of the flag and check that it did: 518 time.Sleep(200 * time.Millisecond) 519 Expect(flag.Raised()).To(BeTrue()) 520 521 // Steal the flag updating the database directly: 522 _, err = dbHandle.Exec(` 523 update 524 leadership_flags 525 set 526 holder = 'your_process', 527 version = version + 1, 528 timestamp = now() 529 where 530 name = 'my_flag' 531 `) 532 Expect(err).ToNot(HaveOccurred()) 533 534 // Give the process some time to detect the situation and then check that it 535 // lowers the flag: 536 time.Sleep(200 * time.Millisecond) 537 Expect(flag.Raised()).To(BeFalse()) 538 }) 539 }) 540 541 When("Stolen and then expired", func() { 542 It("Is recovers and raises", func() { 543 var err error 544 545 // Create the flag: 546 flag, err := NewFlag(). 547 Logger(logger). 548 Handle(dbHandle). 549 Name("my_flag"). 550 Process("my_process"). 551 Interval(200 * time.Millisecond). 552 Jitter(0). 553 Build(ctx) 554 Expect(err).ToNot(HaveOccurred()) 555 defer func() { 556 err = flag.Close() 557 Expect(err).ToNot(HaveOccurred()) 558 }() 559 560 // Give the process some time to get hold of the flag and then check that 561 // it did: 562 time.Sleep(200 * time.Millisecond) 563 Expect(flag.Raised()).To(BeTrue()) 564 565 // Force another holder updating the database directly: 566 _, err = dbHandle.Exec(` 567 update 568 leadership_flags 569 set 570 holder = 'your_process', 571 version = version + 1, 572 timestamp = now() 573 where 574 name = 'my_flag' 575 `) 576 Expect(err).ToNot(HaveOccurred()) 577 578 // Give the process some time to detect the situation and check that it 579 // lowers the flag: 580 time.Sleep(200 * time.Millisecond) 581 Expect(flag.Raised()).To(BeFalse()) 582 583 // Give it more time, so that the forced holder fails to renew and then check 584 // that it recovers and raises it: 585 time.Sleep(200 * time.Millisecond) 586 Expect(flag.Raised()).To(BeTrue()) 587 }) 588 }) 589 590 // These sets of tests are two contesting leadership 591 When("Precheck is defined", func() { 592 var ( 593 flip *Flag 594 flop *Flag 595 condition int32 596 ) 597 const ( 598 flagName = "my_flag" 599 flipProcess = "flip" // This process will have the flag when the precheck condition is true. 600 flopProcess = "flop" // This process will have the flag when the precheck condition is false. 601 intervalMs = 100 602 ) 603 604 BeforeEach(func() { 605 var err error 606 607 // Create the database table: 608 CreateTable() 609 610 // Create the database row so that the flag is already held by another 611 // process that will eventually fail to update it: 612 _, err = dbHandle.Exec(fmt.Sprintf(` 613 insert into leadership_flags ( 614 name, 615 holder, 616 version, 617 timestamp 618 ) values ( 619 '%s', 620 'your_process', 621 123, 622 now() 623 ) 624 `, flagName)) 625 Expect(err).ToNot(HaveOccurred()) 626 627 // Create the object: 628 flip, err = NewFlag(). 629 Logger(logger). 630 Handle(dbHandle). 631 Name(flagName). 632 Process(flipProcess). 633 PrecheckFunc(func() (bool, error) { return atomic.LoadInt32(&condition) != 1, nil }). 634 Interval(intervalMs * time.Millisecond). 635 Jitter(0). 636 Build(ctx) 637 Expect(err).ToNot(HaveOccurred()) 638 639 flop, err = NewFlag(). 640 Logger(logger). 641 Handle(dbHandle). 642 Name(flagName). 643 Process(flopProcess). 644 PrecheckFunc(func() (bool, error) { return atomic.LoadInt32(&condition) == 1, nil }). 645 Interval(intervalMs * time.Millisecond). 646 Jitter(0). 647 Build(ctx) 648 Expect(err).ToNot(HaveOccurred()) 649 }) 650 651 AfterEach(func() { 652 err := flip.Close() 653 Expect(err).ToNot(HaveOccurred()) 654 655 err = flop.Close() 656 Expect(err).ToNot(HaveOccurred()) 657 }) 658 659 It("It is attained by the process with the precheck set to true", func() { 660 time.Sleep(2 * intervalMs * time.Millisecond) 661 Expect(flip.Raised()).To(BeTrue(), "Flip is expected to have the flag") 662 Expect(flop.Raised()).To(BeFalse(), "Flop is expected to not have the flag") 663 664 By("swapping the precheck condition") 665 atomic.SwapInt32(&condition, 1) 666 667 time.Sleep(4 * intervalMs * time.Millisecond) 668 Expect(flip.Raised()).To(BeFalse(), "Flip is expected to not have the flag after switch") 669 Expect(flop.Raised()).To(BeTrue(), "Flop is expected to have the flag after switch") 670 }) 671 }) 672 }) 673 674 var _ = Describe("Flag metrics enabled", func() { 675 var ctx context.Context 676 var dbObject *Database 677 var dbHandle *sql.DB 678 var metricsServer *MetricsServer 679 680 BeforeEach(func() { 681 // Create a context: 682 ctx = context.Background() 683 684 // Create a database: 685 dbObject = dbServer.MakeDatabase() 686 dbHandle = dbObject.MakeHandle() 687 688 // Create the metrics server: 689 metricsServer = NewMetricsServer() 690 }) 691 692 AfterEach(func() { 693 // Delete the database: 694 dbObject.Close() 695 696 // Stop the metrics server: 697 metricsServer.Close() 698 }) 699 700 It("Generates state metrics", func() { 701 // Create the first process: 702 first, err := NewFlag(). 703 Logger(logger). 704 Handle(dbHandle). 705 Name("my_flag"). 706 Process("first_process"). 707 Interval(200 * time.Millisecond). 708 Jitter(0). 709 MetricsSubsystem("my"). 710 MetricsRegisterer(metricsServer.Registry()). 711 Build(ctx) 712 Expect(err).ToNot(HaveOccurred()) 713 defer func() { 714 err = first.Close() 715 Expect(err).ToNot(HaveOccurred()) 716 }() 717 718 // Give it time to raise: 719 time.Sleep(200 * time.Millisecond) 720 Expect(first.Raised()).To(BeTrue()) 721 722 // Create the second process: 723 second, err := NewFlag(). 724 Logger(logger). 725 Handle(dbHandle). 726 Name("my_flag"). 727 Process("second_process"). 728 Interval(200 * time.Millisecond). 729 Jitter(0). 730 MetricsSubsystem("my"). 731 MetricsRegisterer(metricsServer.Registry()). 732 Build(ctx) 733 Expect(err).ToNot(HaveOccurred()) 734 defer func() { 735 err = second.Close() 736 Expect(err).ToNot(HaveOccurred()) 737 }() 738 739 // Git it time to lower: 740 time.Sleep(200 * time.Millisecond) 741 Expect(second.Raised()).To(BeFalse()) 742 743 // Verify the metrics: 744 metrics := metricsServer.Metrics() 745 Expect(metrics).To(MatchLine(`^my_leadership_flag_state\{name="my_flag",process="first_process"\} 1$`)) 746 Expect(metrics).To(MatchLine(`^my_leadership_flag_state\{name="my_flag",process="second_process"\} 0$`)) 747 }) 748 })