vitess.io/vitess@v0.16.2/go/vt/topo/shard_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     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 topo
    18  
    19  import (
    20  	"context"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/stretchr/testify/assert"
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"vitess.io/vitess/go/test/utils"
    28  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    29  )
    30  
    31  // This file tests the shard related object functionnalities.
    32  
    33  func TestAddCells(t *testing.T) {
    34  	var cells []string
    35  
    36  	// no restriction + no restriction -> no restrictions
    37  	cells = addCells(cells, nil)
    38  	if cells != nil {
    39  		t.Fatalf("addCells(no restriction)+no restriction should be no restriction")
    40  	}
    41  
    42  	// no restriction + cells -> no restrictions
    43  	cells = addCells(cells, []string{"c1", "c2"})
    44  	if cells != nil {
    45  		t.Fatalf("addCells(no restriction)+restriction should be no restriction")
    46  	}
    47  
    48  	// cells + no restriction -> no restrictions
    49  	cells = []string{"c1", "c2"}
    50  	cells = addCells(cells, nil)
    51  	if cells != nil {
    52  		t.Fatalf("addCells(restriction)+no restriction should be no restriction")
    53  	}
    54  
    55  	// cells + cells -> union
    56  	cells = []string{"c1", "c2"}
    57  	cells = addCells(cells, []string{"c2", "c3"})
    58  	if !reflect.DeepEqual(cells, []string{"c1", "c2", "c3"}) {
    59  		t.Fatalf("addCells(restriction)+restriction failed: got %v", cells)
    60  	}
    61  }
    62  
    63  func TestRemoveCellsFromList(t *testing.T) {
    64  	var cells []string
    65  	allCells := []string{"first", "second", "third"}
    66  
    67  	// remove from empty list should return allCells - what we remove
    68  	cells = removeCellsFromList([]string{"second"}, allCells)
    69  	if !reflect.DeepEqual(cells, []string{"first", "third"}) {
    70  		t.Fatalf("removeCells(full)-second failed: got %v", allCells)
    71  	}
    72  
    73  	// removethe next two cells, should return empty list
    74  	cells = removeCellsFromList(cells, []string{"first", "third"})
    75  	if len(cells) != 0 {
    76  		t.Fatalf("removeCells(full)-first-third is not empty: %v", cells)
    77  	}
    78  }
    79  
    80  func TestRemoveCells(t *testing.T) {
    81  	var cells []string
    82  	allCells := []string{"first", "second", "third"}
    83  
    84  	// remove from empty list should return allCells - what we remove
    85  	var emptyResult bool
    86  	cells, emptyResult = removeCells(cells, []string{"second"}, allCells)
    87  	if emptyResult || !reflect.DeepEqual(cells, []string{"first", "third"}) {
    88  		t.Fatalf("removeCells(full)-second failed: got %v", cells)
    89  	}
    90  
    91  	// removethe next two cells, should return empty list
    92  	cells, emptyResult = removeCells(cells, []string{"first", "third"}, allCells)
    93  	if !emptyResult {
    94  		t.Fatalf("removeCells(full)-first-third is not empty: %v", cells)
    95  	}
    96  }
    97  
    98  func lockedKeyspaceContext(keyspace string) context.Context {
    99  	ctx := context.Background()
   100  	return context.WithValue(ctx, locksKey, &locksInfo{
   101  		info: map[string]*lockInfo{
   102  			// An empty entry is good enough for this.
   103  			keyspace: {},
   104  		},
   105  	})
   106  }
   107  
   108  func addToDenyList(ctx context.Context, si *ShardInfo, tabletType topodatapb.TabletType, cells, tables []string) error {
   109  	if err := si.UpdateSourceDeniedTables(ctx, tabletType, cells, false, tables); err != nil {
   110  		return err
   111  	}
   112  	return nil
   113  }
   114  
   115  func removeFromDenyList(ctx context.Context, si *ShardInfo, tabletType topodatapb.TabletType, cells, tables []string) error {
   116  	if err := si.UpdateSourceDeniedTables(ctx, tabletType, cells, true, tables); err != nil {
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  func validateDenyList(t *testing.T, si *ShardInfo, tabletType topodatapb.TabletType, cells, tables []string) {
   123  	tc := si.GetTabletControl(tabletType)
   124  	require.ElementsMatch(t, tc.Cells, cells)
   125  	require.ElementsMatch(t, tc.DeniedTables, tables)
   126  }
   127  
   128  func TestUpdateSourcePrimaryDeniedTables(t *testing.T) {
   129  	primary := topodatapb.TabletType_PRIMARY
   130  	si := NewShardInfo("ks", "sh", &topodatapb.Shard{}, nil)
   131  	ctx := lockedKeyspaceContext("ks")
   132  	t1, t2, t3, t4 := "t1", "t2", "t3", "t4"
   133  	tables1 := []string{t1, t2}
   134  	tables2 := []string{t3, t4}
   135  
   136  	require.NoError(t, addToDenyList(ctx, si, primary, nil, tables1))
   137  	validateDenyList(t, si, primary, nil, tables1)
   138  
   139  	require.NoError(t, addToDenyList(ctx, si, primary, nil, tables2))
   140  	validateDenyList(t, si, primary, nil, append(tables1, tables2...))
   141  
   142  	require.Error(t, addToDenyList(ctx, si, primary, nil, tables2), dlTablesAlreadyPresent)
   143  	require.Error(t, addToDenyList(ctx, si, primary, nil, []string{t1}), dlTablesAlreadyPresent)
   144  
   145  	require.NoError(t, removeFromDenyList(ctx, si, primary, nil, tables2))
   146  	validateDenyList(t, si, primary, nil, tables1)
   147  
   148  	require.Error(t, removeFromDenyList(ctx, si, primary, nil, tables2), dlTablesNotPresent)
   149  	require.Error(t, removeFromDenyList(ctx, si, primary, nil, []string{t3}), dlTablesNotPresent)
   150  	validateDenyList(t, si, primary, nil, tables1)
   151  
   152  	require.NoError(t, removeFromDenyList(ctx, si, primary, nil, []string{t1}))
   153  	require.NoError(t, removeFromDenyList(ctx, si, primary, nil, []string{t2}))
   154  	require.Nil(t, si.GetTabletControl(primary))
   155  
   156  	require.Error(t, addToDenyList(ctx, si, primary, []string{"cell"}, tables1), dlNoCellsForPrimary)
   157  }
   158  
   159  func TestUpdateSourceDeniedTables(t *testing.T) {
   160  	si := NewShardInfo("ks", "sh", &topodatapb.Shard{}, nil)
   161  
   162  	// check we enforce the keyspace lock
   163  	ctx := context.Background()
   164  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, nil, false, nil); err == nil || err.Error() != "keyspace ks is not locked (no locksInfo)" {
   165  		t.Fatalf("unlocked keyspace produced wrong error: %v", err)
   166  	}
   167  	ctx = lockedKeyspaceContext("ks")
   168  
   169  	// add one cell
   170  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"first"}, false, []string{"t1", "t2"}); err != nil || !reflect.DeepEqual(si.TabletControls, []*topodatapb.Shard_TabletControl{
   171  		{
   172  			TabletType:   topodatapb.TabletType_RDONLY,
   173  			Cells:        []string{"first"},
   174  			DeniedTables: []string{"t1", "t2"},
   175  		},
   176  	}) {
   177  		t.Fatalf("one cell add failed: %v", si)
   178  	}
   179  
   180  	// remove that cell, going back
   181  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"first"}, true, nil); err != nil || len(si.TabletControls) != 0 {
   182  		t.Fatalf("going back should have remove the record: %v", si)
   183  	}
   184  
   185  	// re-add a cell, then another with different table list to
   186  	// make sure it fails
   187  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"first"}, false, []string{"t1", "t2"}); err != nil {
   188  		t.Fatalf("one cell add failed: %v", si)
   189  	}
   190  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"second"}, false, []string{"t2", "t3"}); err == nil || err.Error() != "trying to use two different sets of denied tables for shard ks/sh: [t1 t2] and [t2 t3]" {
   191  		t.Fatalf("different table list should fail: %v", err)
   192  	}
   193  	// add another cell, see the list grow
   194  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"second"}, false, []string{"t1", "t2"}); err != nil || !reflect.DeepEqual(si.TabletControls, []*topodatapb.Shard_TabletControl{
   195  		{
   196  			TabletType:   topodatapb.TabletType_RDONLY,
   197  			Cells:        []string{"first", "second"},
   198  			DeniedTables: []string{"t1", "t2"},
   199  		},
   200  	}) {
   201  		t.Fatalf("second cell add failed: %v", si)
   202  	}
   203  
   204  	// add all cells, see the list grow to all
   205  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"first", "second", "third"}, false, []string{"t1", "t2"}); err != nil || !reflect.DeepEqual(si.TabletControls, []*topodatapb.Shard_TabletControl{
   206  		{
   207  			TabletType:   topodatapb.TabletType_RDONLY,
   208  			Cells:        []string{"first", "second", "third"},
   209  			DeniedTables: []string{"t1", "t2"},
   210  		},
   211  	}) {
   212  		t.Fatalf("all cells add failed: %v", si)
   213  	}
   214  
   215  	// remove one cell from the full list
   216  	if err := si.UpdateSourceDeniedTables(ctx, topodatapb.TabletType_RDONLY, []string{"second"}, true, []string{"t1", "t2"}); err != nil || !reflect.DeepEqual(si.TabletControls, []*topodatapb.Shard_TabletControl{
   217  		{
   218  			TabletType:   topodatapb.TabletType_RDONLY,
   219  			Cells:        []string{"first", "third"},
   220  			DeniedTables: []string{"t1", "t2"},
   221  		},
   222  	}) {
   223  		t.Fatalf("one cell removal from all failed: %v", si)
   224  	}
   225  }
   226  
   227  func TestValidateShardName(t *testing.T) {
   228  	t.Parallel()
   229  
   230  	cases := []struct {
   231  		name          string
   232  		expectedRange *topodatapb.KeyRange
   233  		valid         bool
   234  	}{
   235  		{
   236  			name:  "0",
   237  			valid: true,
   238  		},
   239  		{
   240  			name: "-80",
   241  			expectedRange: &topodatapb.KeyRange{
   242  				Start: nil,
   243  				End:   []byte{0x80},
   244  			},
   245  			valid: true,
   246  		},
   247  		{
   248  			name: "40-80",
   249  			expectedRange: &topodatapb.KeyRange{
   250  				Start: []byte{0x40},
   251  				End:   []byte{0x80},
   252  			},
   253  			valid: true,
   254  		},
   255  		{
   256  			name:  "foo-bar",
   257  			valid: false,
   258  		},
   259  		{
   260  			name:  "a/b",
   261  			valid: false,
   262  		},
   263  	}
   264  
   265  	for _, tcase := range cases {
   266  		tcase := tcase
   267  		t.Run(tcase.name, func(t *testing.T) {
   268  			t.Parallel()
   269  
   270  			_, kr, err := ValidateShardName(tcase.name)
   271  			if !tcase.valid {
   272  				assert.Error(t, err, "expected %q to be an invalid shard name", tcase.name)
   273  				return
   274  			}
   275  
   276  			require.NoError(t, err, "expected %q to be a valid shard name, got error: %v", tcase.name, err)
   277  			utils.MustMatch(t, tcase.expectedRange, kr)
   278  		})
   279  	}
   280  }