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  }