github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/config/zonepb/zone_test.go (about) 1 // Copyright 2015 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package zonepb 12 13 import ( 14 "fmt" 15 "math/rand" 16 "reflect" 17 "testing" 18 19 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 20 "github.com/cockroachdb/cockroach/pkg/testutils" 21 "github.com/cockroachdb/cockroach/pkg/util/leaktest" 22 "github.com/cockroachdb/cockroach/pkg/util/timeutil" 23 proto "github.com/gogo/protobuf/proto" 24 "github.com/stretchr/testify/require" 25 yaml "gopkg.in/yaml.v2" 26 ) 27 28 func TestZoneConfigValidate(t *testing.T) { 29 defer leaktest.AfterTest(t)() 30 31 testCases := []struct { 32 cfg ZoneConfig 33 expected string 34 }{ 35 { 36 ZoneConfig{ 37 NumReplicas: proto.Int32(0), 38 }, 39 "at least one replica is required", 40 }, 41 { 42 ZoneConfig{ 43 NumReplicas: proto.Int32(-1), 44 }, 45 "at least one replica is required", 46 }, 47 { 48 ZoneConfig{ 49 NumReplicas: proto.Int32(2), 50 }, 51 "at least 3 replicas are required for multi-replica configurations", 52 }, 53 { 54 ZoneConfig{ 55 NumReplicas: proto.Int32(1), 56 RangeMaxBytes: proto.Int64(0), 57 }, 58 "RangeMaxBytes 0 less than minimum allowed", 59 }, 60 { 61 ZoneConfig{ 62 NumReplicas: proto.Int32(1), 63 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 64 GC: &GCPolicy{TTLSeconds: 0}, 65 }, 66 "GC.TTLSeconds 0 less than minimum allowed", 67 }, 68 { 69 ZoneConfig{ 70 NumReplicas: proto.Int32(1), 71 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 72 GC: &GCPolicy{TTLSeconds: 1}, 73 }, 74 "", 75 }, 76 { 77 ZoneConfig{ 78 NumReplicas: proto.Int32(1), 79 RangeMinBytes: proto.Int64(-1), 80 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 81 GC: &GCPolicy{TTLSeconds: 1}, 82 }, 83 "RangeMinBytes -1 less than minimum allowed", 84 }, 85 { 86 ZoneConfig{ 87 NumReplicas: proto.Int32(1), 88 RangeMinBytes: DefaultZoneConfig().RangeMaxBytes, 89 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 90 GC: &GCPolicy{TTLSeconds: 1}, 91 }, 92 "is greater than or equal to RangeMaxBytes", 93 }, 94 { 95 ZoneConfig{ 96 NumReplicas: proto.Int32(1), 97 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 98 GC: &GCPolicy{TTLSeconds: 1}, 99 Constraints: []ConstraintsConjunction{ 100 {Constraints: []Constraint{{Value: "a", Type: Constraint_DEPRECATED_POSITIVE}}}, 101 }, 102 }, 103 "constraints must either be required .+ or prohibited .+", 104 }, 105 { 106 ZoneConfig{ 107 NumReplicas: proto.Int32(1), 108 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 109 GC: &GCPolicy{TTLSeconds: 1}, 110 Constraints: []ConstraintsConjunction{ 111 {Constraints: []Constraint{{Value: "a", Type: Constraint_PROHIBITED}}}, 112 }, 113 }, 114 "", 115 }, 116 { 117 ZoneConfig{ 118 NumReplicas: proto.Int32(1), 119 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 120 GC: &GCPolicy{TTLSeconds: 1}, 121 Constraints: []ConstraintsConjunction{ 122 { 123 Constraints: []Constraint{{Value: "a", Type: Constraint_PROHIBITED}}, 124 NumReplicas: 1, 125 }, 126 }, 127 }, 128 "", 129 }, 130 { 131 ZoneConfig{ 132 NumReplicas: proto.Int32(1), 133 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 134 GC: &GCPolicy{TTLSeconds: 1}, 135 Constraints: []ConstraintsConjunction{ 136 { 137 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 138 NumReplicas: 2, 139 }, 140 }, 141 }, 142 "the number of replicas specified in constraints .+ cannot be greater than", 143 }, 144 { 145 ZoneConfig{ 146 NumReplicas: proto.Int32(3), 147 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 148 GC: &GCPolicy{TTLSeconds: 1}, 149 Constraints: []ConstraintsConjunction{ 150 { 151 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 152 NumReplicas: 2, 153 }, 154 }, 155 }, 156 "", 157 }, 158 { 159 ZoneConfig{ 160 NumReplicas: proto.Int32(1), 161 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 162 GC: &GCPolicy{TTLSeconds: 1}, 163 Constraints: []ConstraintsConjunction{ 164 { 165 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 166 NumReplicas: 0, 167 }, 168 { 169 Constraints: []Constraint{{Value: "b", Type: Constraint_REQUIRED}}, 170 NumReplicas: 1, 171 }, 172 }, 173 }, 174 "constraints must apply to at least one replica", 175 }, 176 { 177 ZoneConfig{ 178 NumReplicas: proto.Int32(3), 179 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 180 GC: &GCPolicy{TTLSeconds: 1}, 181 Constraints: []ConstraintsConjunction{ 182 { 183 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 184 NumReplicas: 2, 185 }, 186 { 187 Constraints: []Constraint{{Value: "b", Type: Constraint_PROHIBITED}}, 188 NumReplicas: 1, 189 }, 190 }, 191 }, 192 "only required constraints .+ can be applied to a subset of replicas", 193 }, 194 { 195 ZoneConfig{ 196 NumReplicas: proto.Int32(1), 197 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 198 GC: &GCPolicy{TTLSeconds: 1}, 199 LeasePreferences: []LeasePreference{ 200 { 201 Constraints: []Constraint{}, 202 }, 203 }, 204 }, 205 "every lease preference must include at least one constraint", 206 }, 207 { 208 ZoneConfig{ 209 NumReplicas: proto.Int32(1), 210 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 211 GC: &GCPolicy{TTLSeconds: 1}, 212 LeasePreferences: []LeasePreference{ 213 { 214 Constraints: []Constraint{{Value: "a", Type: Constraint_DEPRECATED_POSITIVE}}, 215 }, 216 }, 217 }, 218 "lease preference constraints must either be required .+ or prohibited .+", 219 }, 220 { 221 ZoneConfig{ 222 NumReplicas: proto.Int32(1), 223 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 224 GC: &GCPolicy{TTLSeconds: 1}, 225 LeasePreferences: []LeasePreference{ 226 { 227 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 228 }, 229 { 230 Constraints: []Constraint{{Value: "b", Type: Constraint_PROHIBITED}}, 231 }, 232 }, 233 }, 234 "", 235 }, 236 } 237 238 for i, c := range testCases { 239 err := c.cfg.Validate() 240 if !testutils.IsError(err, c.expected) { 241 t.Errorf("%d: expected %q, got %v", i, c.expected, err) 242 } 243 } 244 } 245 246 func TestZoneConfigValidateTandemFields(t *testing.T) { 247 defer leaktest.AfterTest(t)() 248 249 testCases := []struct { 250 cfg ZoneConfig 251 expected string 252 }{ 253 { 254 ZoneConfig{ 255 RangeMaxBytes: DefaultZoneConfig().RangeMaxBytes, 256 }, 257 "range_min_bytes and range_max_bytes must be set together", 258 }, 259 { 260 ZoneConfig{ 261 RangeMinBytes: DefaultZoneConfig().RangeMinBytes, 262 }, 263 "range_min_bytes and range_max_bytes must be set together", 264 }, 265 { 266 ZoneConfig{ 267 Constraints: []ConstraintsConjunction{ 268 { 269 Constraints: []Constraint{{Value: "a", Type: Constraint_REQUIRED}}, 270 NumReplicas: 2, 271 }, 272 }, 273 }, 274 "when per-replica constraints are set, num_replicas must be set as well", 275 }, 276 { 277 ZoneConfig{ 278 InheritedConstraints: true, 279 InheritedLeasePreferences: false, 280 LeasePreferences: []LeasePreference{ 281 { 282 Constraints: []Constraint{}, 283 }, 284 }, 285 }, 286 "lease preferences can not be set unless the constraints are explicitly set as well", 287 }, 288 } 289 290 for i, c := range testCases { 291 err := c.cfg.ValidateTandemFields() 292 if !testutils.IsError(err, c.expected) { 293 t.Errorf("%d: expected %q, got %v", i, c.expected, err) 294 } 295 } 296 } 297 298 func TestZoneConfigSubzones(t *testing.T) { 299 defer leaktest.AfterTest(t)() 300 301 zone := DefaultZoneConfig() 302 subzoneAInvalid := Subzone{IndexID: 1, PartitionName: "a", Config: ZoneConfig{NumReplicas: proto.Int32(-1)}} 303 subzoneA := Subzone{IndexID: 1, PartitionName: "a", Config: zone} 304 subzoneB := Subzone{IndexID: 1, PartitionName: "b", Config: zone} 305 306 if zone.IsSubzonePlaceholder() { 307 t.Errorf("default zone config should not be considered a subzone placeholder") 308 } 309 310 zone.SetSubzone(subzoneAInvalid) 311 zone.SetSubzone(subzoneB) 312 if err := zone.Validate(); !testutils.IsError(err, "at least one replica is required") { 313 t.Errorf("expected 'at least one replica is required' validation error, but got %v", err) 314 } 315 316 zone.SetSubzone(subzoneA) 317 if err := zone.Validate(); err != nil { 318 t.Errorf("expected zone validation to succeed, but got %s", err) 319 } 320 if subzone := zone.GetSubzone(1, "a"); !subzoneA.Equal(subzone) { 321 t.Errorf("expected subzone to equal %+v, but got %+v", &subzoneA, subzone) 322 } 323 if subzone := zone.GetSubzone(1, "b"); !subzoneB.Equal(subzone) { 324 t.Errorf("expected subzone to equal %+v, but got %+v", &subzoneB, subzone) 325 } 326 if subzone := zone.GetSubzone(1, "c"); subzone != nil { 327 t.Errorf("expected nil subzone, but got %+v", subzone) 328 } 329 if subzone := zone.GetSubzone(2, "a"); subzone != nil { 330 t.Errorf("expected nil subzone, but got %+v", subzone) 331 } 332 if zone.IsSubzonePlaceholder() { 333 t.Errorf("zone with its own config should not be considered a subzone placeholder") 334 } 335 336 zone.DeleteTableConfig() 337 if e := (ZoneConfig{ 338 NumReplicas: proto.Int32(0), 339 Subzones: []Subzone{subzoneA, subzoneB}, 340 }); !e.Equal(zone) { 341 t.Errorf("expected zone after clearing to equal %+v, but got %+v", e, zone) 342 } 343 if !zone.IsSubzonePlaceholder() { 344 t.Errorf("expected cleared zone config to be considered a subzone placeholder") 345 } 346 347 if didDelete := zone.DeleteSubzone(1, "c"); didDelete { 348 t.Errorf("deletion claims to have succeeded on non-existent subzone") 349 } 350 if didDelete := zone.DeleteSubzone(1, "b"); !didDelete { 351 t.Errorf("valid deletion claims to have failed") 352 } 353 if subzone := zone.GetSubzone(1, "b"); subzone != nil { 354 t.Errorf("expected deleted subzone to be nil when retrieved, but got %+v", subzone) 355 } 356 if subzone := zone.GetSubzone(1, "a"); !subzoneA.Equal(subzone) { 357 t.Errorf("expected non-deleted subzone to equal %+v, but got %+v", &subzoneA, subzone) 358 } 359 360 zone.SetSubzone(Subzone{IndexID: 2, Config: DefaultZoneConfig()}) 361 zone.SetSubzone(Subzone{IndexID: 2, PartitionName: "a", Config: DefaultZoneConfig()}) 362 zone.SetSubzone(subzoneB) // interleave a subzone from a different index 363 zone.SetSubzone(Subzone{IndexID: 2, PartitionName: "b", Config: DefaultZoneConfig()}) 364 if e, a := 5, len(zone.Subzones); e != a { 365 t.Fatalf("expected %d subzones, but found %d", e, a) 366 } 367 if err := zone.Validate(); err != nil { 368 t.Errorf("expected zone validation to succeed, but got %s", err) 369 } 370 371 zone.DeleteIndexSubzones(2) 372 if e, a := 2, len(zone.Subzones); e != a { 373 t.Fatalf("expected %d subzones, but found %d", e, a) 374 } 375 for _, subzone := range zone.Subzones { 376 if subzone.IndexID == 2 { 377 t.Fatalf("expected no subzones to refer to index 2, but found %+v", subzone) 378 } 379 } 380 if err := zone.Validate(); err != nil { 381 t.Errorf("expected zone validation to succeed, but got %s", err) 382 } 383 } 384 385 // TestZoneConfigMarshalYAML makes sure that ZoneConfig is correctly marshaled 386 // to YAML and back. 387 func TestZoneConfigMarshalYAML(t *testing.T) { 388 defer leaktest.AfterTest(t)() 389 390 original := ZoneConfig{ 391 RangeMinBytes: proto.Int64(1), 392 RangeMaxBytes: proto.Int64(1), 393 GC: &GCPolicy{ 394 TTLSeconds: 1, 395 }, 396 NumReplicas: proto.Int32(1), 397 } 398 399 testCases := []struct { 400 constraints []ConstraintsConjunction 401 leasePreferences []LeasePreference 402 expected string 403 }{ 404 { 405 expected: `range_min_bytes: 1 406 range_max_bytes: 1 407 gc: 408 ttlseconds: 1 409 num_replicas: 1 410 constraints: [] 411 lease_preferences: [] 412 `, 413 }, 414 { 415 constraints: []ConstraintsConjunction{ 416 { 417 Constraints: []Constraint{ 418 { 419 Type: Constraint_REQUIRED, 420 Key: "duck", 421 Value: "foo", 422 }, 423 }, 424 }, 425 }, 426 expected: `range_min_bytes: 1 427 range_max_bytes: 1 428 gc: 429 ttlseconds: 1 430 num_replicas: 1 431 constraints: [+duck=foo] 432 lease_preferences: [] 433 `, 434 }, 435 { 436 constraints: []ConstraintsConjunction{ 437 { 438 Constraints: []Constraint{ 439 { 440 Type: Constraint_DEPRECATED_POSITIVE, 441 Value: "foo", 442 }, 443 { 444 Type: Constraint_REQUIRED, 445 Key: "duck", 446 Value: "foo", 447 }, 448 { 449 Type: Constraint_PROHIBITED, 450 Key: "duck", 451 Value: "foo", 452 }, 453 }, 454 }, 455 }, 456 expected: `range_min_bytes: 1 457 range_max_bytes: 1 458 gc: 459 ttlseconds: 1 460 num_replicas: 1 461 constraints: [foo, +duck=foo, -duck=foo] 462 lease_preferences: [] 463 `, 464 }, 465 { 466 constraints: []ConstraintsConjunction{ 467 { 468 NumReplicas: 3, 469 Constraints: []Constraint{ 470 { 471 Type: Constraint_REQUIRED, 472 Key: "duck", 473 Value: "foo", 474 }, 475 }, 476 }, 477 }, 478 expected: `range_min_bytes: 1 479 range_max_bytes: 1 480 gc: 481 ttlseconds: 1 482 num_replicas: 1 483 constraints: {+duck=foo: 3} 484 lease_preferences: [] 485 `, 486 }, 487 { 488 constraints: []ConstraintsConjunction{ 489 { 490 NumReplicas: 3, 491 Constraints: []Constraint{ 492 { 493 Type: Constraint_DEPRECATED_POSITIVE, 494 Value: "foo", 495 }, 496 { 497 Type: Constraint_REQUIRED, 498 Key: "duck", 499 Value: "foo", 500 }, 501 { 502 Type: Constraint_PROHIBITED, 503 Key: "duck", 504 Value: "foo", 505 }, 506 }, 507 }, 508 }, 509 expected: `range_min_bytes: 1 510 range_max_bytes: 1 511 gc: 512 ttlseconds: 1 513 num_replicas: 1 514 constraints: {'foo,+duck=foo,-duck=foo': 3} 515 lease_preferences: [] 516 `, 517 }, 518 { 519 constraints: []ConstraintsConjunction{ 520 { 521 NumReplicas: 1, 522 Constraints: []Constraint{ 523 { 524 Type: Constraint_REQUIRED, 525 Key: "duck", 526 Value: "bar1", 527 }, 528 { 529 Type: Constraint_REQUIRED, 530 Key: "duck", 531 Value: "bar2", 532 }, 533 }, 534 }, 535 { 536 NumReplicas: 2, 537 Constraints: []Constraint{ 538 { 539 Type: Constraint_REQUIRED, 540 Key: "duck", 541 Value: "foo", 542 }, 543 }, 544 }, 545 }, 546 expected: `range_min_bytes: 1 547 range_max_bytes: 1 548 gc: 549 ttlseconds: 1 550 num_replicas: 1 551 constraints: {'+duck=bar1,+duck=bar2': 1, +duck=foo: 2} 552 lease_preferences: [] 553 `, 554 }, 555 { 556 leasePreferences: []LeasePreference{}, 557 expected: `range_min_bytes: 1 558 range_max_bytes: 1 559 gc: 560 ttlseconds: 1 561 num_replicas: 1 562 constraints: [] 563 lease_preferences: [] 564 `, 565 }, 566 { 567 leasePreferences: []LeasePreference{ 568 { 569 Constraints: []Constraint{ 570 { 571 Type: Constraint_REQUIRED, 572 Key: "duck", 573 Value: "foo", 574 }, 575 }, 576 }, 577 }, 578 expected: `range_min_bytes: 1 579 range_max_bytes: 1 580 gc: 581 ttlseconds: 1 582 num_replicas: 1 583 constraints: [] 584 lease_preferences: [[+duck=foo]] 585 `, 586 }, 587 { 588 constraints: []ConstraintsConjunction{ 589 { 590 Constraints: []Constraint{ 591 { 592 Type: Constraint_REQUIRED, 593 Key: "duck", 594 Value: "foo", 595 }, 596 }, 597 }, 598 }, 599 leasePreferences: []LeasePreference{ 600 { 601 Constraints: []Constraint{ 602 { 603 Type: Constraint_REQUIRED, 604 Key: "duck", 605 Value: "bar1", 606 }, 607 { 608 Type: Constraint_REQUIRED, 609 Key: "duck", 610 Value: "bar2", 611 }, 612 }, 613 }, 614 { 615 Constraints: []Constraint{ 616 { 617 Type: Constraint_PROHIBITED, 618 Key: "duck", 619 Value: "foo", 620 }, 621 }, 622 }, 623 }, 624 expected: `range_min_bytes: 1 625 range_max_bytes: 1 626 gc: 627 ttlseconds: 1 628 num_replicas: 1 629 constraints: [+duck=foo] 630 lease_preferences: [[+duck=bar1, +duck=bar2], [-duck=foo]] 631 `, 632 }, 633 } 634 635 for _, tc := range testCases { 636 t.Run("", func(t *testing.T) { 637 original.Constraints = tc.constraints 638 original.LeasePreferences = tc.leasePreferences 639 body, err := yaml.Marshal(original) 640 if err != nil { 641 t.Fatal(err) 642 } 643 if string(body) != tc.expected { 644 t.Fatalf("yaml.Marshal(%+v)\ngot:\n%s\nwant:\n%s", original, body, tc.expected) 645 } 646 647 var unmarshaled ZoneConfig 648 if err := yaml.UnmarshalStrict(body, &unmarshaled); err != nil { 649 t.Fatal(err) 650 } 651 if !proto.Equal(&unmarshaled, &original) { 652 t.Errorf("yaml.UnmarshalStrict(%q)\ngot:\n%+v\nwant:\n%+v", body, unmarshaled, original) 653 } 654 }) 655 } 656 } 657 658 // TestExperimentalLeasePreferencesYAML makes sure that we accept the 659 // lease_preferences YAML field both with and without the "experimental_" 660 // prefix. 661 func TestExperimentalLeasePreferencesYAML(t *testing.T) { 662 defer leaktest.AfterTest(t)() 663 664 originalPrefs := []LeasePreference{ 665 {Constraints: []Constraint{{Value: "original", Type: Constraint_REQUIRED}}}, 666 } 667 originalZone := ZoneConfig{ 668 LeasePreferences: originalPrefs, 669 } 670 671 testCases := []struct { 672 input string 673 expected []LeasePreference 674 }{ 675 { 676 input: "", 677 expected: originalPrefs, 678 }, 679 { 680 input: "lease_preferences: []", 681 expected: []LeasePreference{}, 682 }, 683 { 684 input: "experimental_lease_preferences: []", 685 expected: []LeasePreference{}, 686 }, 687 { 688 input: "lease_preferences: [[+a=b]]", 689 expected: []LeasePreference{ 690 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 691 }, 692 }, 693 { 694 input: "experimental_lease_preferences: [[+a=b]]", 695 expected: []LeasePreference{ 696 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 697 }, 698 }, 699 { 700 input: "lease_preferences: [[+a=b],[-c=d]]", 701 expected: []LeasePreference{ 702 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 703 {Constraints: []Constraint{{Key: "c", Value: "d", Type: Constraint_PROHIBITED}}}, 704 }, 705 }, 706 { 707 input: "experimental_lease_preferences: [[+a=b],[-c=d]]", 708 expected: []LeasePreference{ 709 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 710 {Constraints: []Constraint{{Key: "c", Value: "d", Type: Constraint_PROHIBITED}}}, 711 }, 712 }, 713 { 714 input: "lease_preferences: [[+c=d]]\nexperimental_lease_preferences: [[+a=b]]", 715 expected: []LeasePreference{ 716 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 717 }, 718 }, 719 { 720 input: "experimental_lease_preferences: [[+a=b]]\nlease_preferences: [[+c=d]]", 721 expected: []LeasePreference{ 722 {Constraints: []Constraint{{Key: "a", Value: "b", Type: Constraint_REQUIRED}}}, 723 }, 724 }, 725 } 726 727 for _, tc := range testCases { 728 zone := originalZone 729 if err := yaml.UnmarshalStrict([]byte(tc.input), &zone); err != nil { 730 t.Fatal(err) 731 } 732 if !reflect.DeepEqual(zone.LeasePreferences, tc.expected) { 733 t.Errorf("unmarshaling %q got %+v; want %+v", tc.input, zone.LeasePreferences, tc.expected) 734 } 735 } 736 } 737 738 func TestConstraintsListYAML(t *testing.T) { 739 defer leaktest.AfterTest(t)() 740 741 testCases := []struct { 742 input string 743 expectErr bool 744 }{ 745 {}, 746 {input: "[]"}, 747 {input: "[+a]"}, 748 {input: "[+a, -b=2, +c=d, e]"}, 749 {input: "{+a: 1}"}, 750 {input: "{+a: 1, '+a=1,+b,+c=d': 2}"}, 751 {input: "{'+a: 1'}"}, // unfortunately this parses just fine because yaml autoconverts it to a list... 752 {input: "{+a,+b: 1}"}, // this also parses fine but will fail ZoneConfig.Validate() 753 {input: "{+a: 1, '+a=1,+b,+c=d': b}", expectErr: true}, 754 {input: "[+a: 1]", expectErr: true}, 755 {input: "[+a: 1, '+a=1,+b,+c=d': 2]", expectErr: true}, 756 {input: "{\"+a=1,+b=2\": 1}"}, // this will work in SQL: constraints='{"+a=1,+b=2": 1}' 757 {input: "{\"+a=1,+b=2,+c\": 1}"}, // won't work in SQL: constraints='{"+a=1,+b=2,+c": 1}' 758 {input: "{'+a=1,+b=2,+c': 1}"}, // this will work in SQL: constraints=e'{\'+a=1,+b=2,+c\': 1}' 759 } 760 761 for _, tc := range testCases { 762 t.Run(tc.input, func(t *testing.T) { 763 var constraints ConstraintsList 764 err := yaml.UnmarshalStrict([]byte(tc.input), &constraints) 765 if err == nil && tc.expectErr { 766 t.Errorf("expected error, but got constraints %+v", constraints) 767 } 768 if err != nil && !tc.expectErr { 769 t.Errorf("expected success, but got %v", err) 770 } 771 }) 772 } 773 } 774 775 func TestMarshalableZoneConfigRoundTrip(t *testing.T) { 776 defer leaktest.AfterTest(t)() 777 778 original := NewPopulatedZoneConfig( 779 rand.New(rand.NewSource(timeutil.Now().UnixNano())), false /* easy */) 780 marshalable := zoneConfigToMarshalable(*original) 781 roundTripped := zoneConfigFromMarshalable(marshalable, *original) 782 783 if !reflect.DeepEqual(roundTripped, *original) { 784 t.Errorf("round-tripping a ZoneConfig through a marshalableZoneConfig failed:\noriginal:\n%+v\nmarshable:\n%+v\ngot:\n%+v", original, marshalable, roundTripped) 785 } 786 } 787 788 func TestZoneSpecifiers(t *testing.T) { 789 defer leaktest.AfterTest(t)() 790 791 // Simulate exactly two named zones: one named default and one named carl. 792 // N.B. DefaultZoneName must always exist in the mapping; it is treated 793 // specially so that it always appears first in the lookup path. 794 defer func(old map[string]uint32) { NamedZones = old }(NamedZones) 795 NamedZones = map[string]uint32{ 796 DefaultZoneName: 0, 797 "carl": 42, 798 } 799 defer func(old map[uint32]string) { NamedZonesByID = old }(NamedZonesByID) 800 NamedZonesByID = map[uint32]string{ 801 0: DefaultZoneName, 802 42: "carl", 803 } 804 805 // Simulate the following schema: 806 // CREATE DATABASE db; CREATE TABLE db.tbl ... 807 // CREATE DATABASE carl; CREATE TABLE carl.toys ... 808 type namespaceEntry struct { 809 parentID uint32 810 name string 811 } 812 namespace := map[namespaceEntry]uint32{ 813 {0, "db"}: 50, 814 {50, "tbl"}: 51, 815 {0, "carl"}: 55, 816 {55, "toys"}: 56, 817 {9000, "broken_parent"}: 57, 818 } 819 resolveName := func(parentID uint32, name string) (uint32, error) { 820 key := namespaceEntry{parentID, name} 821 if id, ok := namespace[key]; ok { 822 return id, nil 823 } 824 return 0, fmt.Errorf("%q not found", name) 825 } 826 resolveID := func(id uint32) (parentID uint32, name string, err error) { 827 for entry, entryID := range namespace { 828 if id == entryID { 829 return entry.parentID, entry.name, nil 830 } 831 } 832 return 0, "", fmt.Errorf("%d not found", id) 833 } 834 835 for _, tc := range []struct { 836 specifier tree.ZoneSpecifier 837 id int 838 err string 839 }{ 840 {tree.ZoneSpecifier{NamedZone: "default"}, 0, ""}, 841 {tree.ZoneSpecifier{NamedZone: "carl"}, 42, ""}, 842 {tree.ZoneSpecifier{NamedZone: "foo"}, -1, `"foo" is not a built-in zone`}, 843 {tree.ZoneSpecifier{Database: "db"}, 50, ""}, 844 {tree.ZoneSpecifier{NamedZone: "db"}, -1, `"db" is not a built-in zone`}, 845 {tableSpecifier("db", "tbl", "", ""), 51, ""}, 846 {tableSpecifier("db", "tbl", "", "prt"), 51, ""}, 847 {tableSpecifier("db", "tbl", "primary", ""), 51, ""}, 848 {tableSpecifier("db", "tbl", "idx", ""), 51, ""}, 849 {tableSpecifier("db", "tbl", "idx", "prt"), 51, ""}, 850 {tree.ZoneSpecifier{Database: "tbl"}, -1, `"tbl" not found`}, 851 {tree.ZoneSpecifier{Database: "carl"}, 55, ""}, 852 {tableSpecifier("carl", "toys", "", ""), 56, ""}, 853 {tableSpecifier("carl", "love", "", ""), -1, `"love" not found`}, 854 } { 855 t.Run(fmt.Sprintf("resolve-specifier=%s", tc.specifier.String()), func(t *testing.T) { 856 err := func() error { 857 id, err := ResolveZoneSpecifier(&tc.specifier, resolveName) 858 if err != nil { 859 return err 860 } 861 if e, a := tc.id, int(id); a != e { 862 t.Errorf("path %d did not match expected path %d", a, e) 863 } 864 return nil 865 }() 866 if !testutils.IsError(err, tc.err) { 867 t.Errorf("expected error matching %q, but got %v", tc.err, err) 868 } 869 }) 870 } 871 872 for _, tc := range []struct { 873 id uint32 874 target string 875 err string 876 }{ 877 {0, "RANGE default", ""}, 878 {41, "", "41 not found"}, 879 {42, "RANGE carl", ""}, 880 {50, "DATABASE db", ""}, 881 {51, "TABLE db.public.tbl", ""}, 882 {55, "DATABASE carl", ""}, 883 {56, "TABLE carl.public.toys", ""}, 884 {57, "", "9000 not found"}, 885 {58, "", "58 not found"}, 886 } { 887 t.Run(fmt.Sprintf("resolve-id=%d", tc.id), func(t *testing.T) { 888 zs, err := ZoneSpecifierFromID(tc.id, resolveID) 889 if !testutils.IsError(err, tc.err) { 890 t.Errorf("unable to lookup ID %d: %s", tc.id, err) 891 } 892 if tc.err != "" { 893 return 894 } 895 if e, a := tc.target, zs.String(); e != a { 896 t.Errorf("expected %q zone name for ID %d, but got %q", e, tc.id, a) 897 } 898 }) 899 } 900 } 901 902 func tableSpecifier( 903 db tree.Name, tbl tree.Name, idx tree.UnrestrictedName, partition tree.Name, 904 ) tree.ZoneSpecifier { 905 return tree.ZoneSpecifier{ 906 TableOrIndex: tree.TableIndexName{ 907 Table: tree.MakeTableName(db, tbl), 908 Index: idx, 909 }, 910 Partition: partition, 911 } 912 } 913 914 // TestDefaultRangeSizesAreSane is a sanity check test to ensure that the values 915 // in the default zone configs are what the author intended. 916 func TestDefaultRangeSizesAreSane(t *testing.T) { 917 require.Regexp(t, "range_min_bytes:134217728 range_max_bytes:536870912", 918 DefaultSystemZoneConfigRef().String()) 919 require.Regexp(t, "range_min_bytes:134217728 range_max_bytes:536870912", 920 DefaultZoneConfigRef().String()) 921 }