github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/storageccl/import_test.go (about)

     1  // Copyright 2017 The Cockroach Authors.
     2  //
     3  // Licensed as a CockroachDB Enterprise file under the Cockroach Community
     4  // License (the "License"); you may not use this file except in compliance with
     5  // the License. You may obtain a copy of the License at
     6  //
     7  //     https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt
     8  
     9  package storageccl
    10  
    11  import (
    12  	"context"
    13  	"fmt"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  	"reflect"
    18  	"strconv"
    19  	"sync/atomic"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/cockroachdb/cockroach/pkg/base"
    24  	"github.com/cockroachdb/cockroach/pkg/keys"
    25  	"github.com/cockroachdb/cockroach/pkg/kv"
    26  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver"
    27  	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/kvserverbase"
    28  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    29  	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
    30  	"github.com/cockroachdb/cockroach/pkg/sql/sqlbase"
    31  	"github.com/cockroachdb/cockroach/pkg/storage"
    32  	"github.com/cockroachdb/cockroach/pkg/storage/cloud"
    33  	"github.com/cockroachdb/cockroach/pkg/testutils"
    34  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    35  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    36  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    37  	"github.com/cockroachdb/cockroach/pkg/util/leaktest"
    38  	"github.com/cockroachdb/errors"
    39  )
    40  
    41  func TestMaxImportBatchSize(t *testing.T) {
    42  	defer leaktest.AfterTest(t)()
    43  
    44  	testCases := []struct {
    45  		importBatchSize int64
    46  		maxCommandSize  int64
    47  		expected        int64
    48  	}{
    49  		{importBatchSize: 2 << 20, maxCommandSize: 64 << 20, expected: 2 << 20},
    50  		{importBatchSize: 128 << 20, maxCommandSize: 64 << 20, expected: 63 << 20},
    51  		{importBatchSize: 64 << 20, maxCommandSize: 64 << 20, expected: 63 << 20},
    52  		{importBatchSize: 63 << 20, maxCommandSize: 64 << 20, expected: 63 << 20},
    53  	}
    54  	for i, testCase := range testCases {
    55  		st := cluster.MakeTestingClusterSettings()
    56  		importBatchSize.Override(&st.SV, testCase.importBatchSize)
    57  		kvserver.MaxCommandSize.Override(&st.SV, testCase.maxCommandSize)
    58  		if e, a := MaxImportBatchSize(st), testCase.expected; e != a {
    59  			t.Errorf("%d: expected max batch size %d, but got %d", i, e, a)
    60  		}
    61  	}
    62  }
    63  
    64  func slurpSSTablesLatestKey(
    65  	t *testing.T, dir string, paths []string, kr prefixRewriter,
    66  ) []storage.MVCCKeyValue {
    67  	start, end := storage.MVCCKey{Key: keys.MinKey}, storage.MVCCKey{Key: keys.MaxKey}
    68  
    69  	e := storage.NewDefaultInMem()
    70  	defer e.Close()
    71  	batch := e.NewBatch()
    72  	defer batch.Close()
    73  
    74  	for _, path := range paths {
    75  		sst := storage.MakeRocksDBSstFileReader()
    76  		defer sst.Close()
    77  
    78  		fileContents, err := ioutil.ReadFile(filepath.Join(dir, path))
    79  		if err != nil {
    80  			t.Fatalf("%+v", err)
    81  		}
    82  		if err := sst.IngestExternalFile(fileContents); err != nil {
    83  			t.Fatalf("%+v", err)
    84  		}
    85  		if err := sst.Iterate(start.Key, end.Key, func(kv storage.MVCCKeyValue) (bool, error) {
    86  			var ok bool
    87  			kv.Key.Key, ok = kr.rewriteKey(kv.Key.Key)
    88  			if !ok {
    89  				return true, errors.Errorf("could not rewrite key: %s", kv.Key.Key)
    90  			}
    91  			v := roachpb.Value{RawBytes: kv.Value}
    92  			v.ClearChecksum()
    93  			v.InitChecksum(kv.Key.Key)
    94  			if err := batch.Put(kv.Key, v.RawBytes); err != nil {
    95  				return true, err
    96  			}
    97  			return false, nil
    98  		}); err != nil {
    99  			t.Fatalf("%+v", err)
   100  		}
   101  	}
   102  
   103  	var kvs []storage.MVCCKeyValue
   104  	it := batch.NewIterator(storage.IterOptions{UpperBound: roachpb.KeyMax})
   105  	defer it.Close()
   106  	for it.SeekGE(start); ; it.NextKey() {
   107  		if ok, err := it.Valid(); err != nil {
   108  			t.Fatal(err)
   109  		} else if !ok || !it.UnsafeKey().Less(end) {
   110  			break
   111  		}
   112  		kvs = append(kvs, storage.MVCCKeyValue{Key: it.Key(), Value: it.Value()})
   113  	}
   114  	return kvs
   115  }
   116  
   117  func clientKVsToEngineKVs(kvs []kv.KeyValue) []storage.MVCCKeyValue {
   118  	var ret []storage.MVCCKeyValue
   119  	for _, kv := range kvs {
   120  		if kv.Value == nil {
   121  			continue
   122  		}
   123  		k := storage.MVCCKey{
   124  			Key:       kv.Key,
   125  			Timestamp: kv.Value.Timestamp,
   126  		}
   127  		ret = append(ret, storage.MVCCKeyValue{Key: k, Value: kv.Value.RawBytes})
   128  	}
   129  	return ret
   130  }
   131  
   132  func TestImport(t *testing.T) {
   133  	defer leaktest.AfterTest(t)()
   134  	t.Run("batch=default", func(t *testing.T) {
   135  		runTestImport(t, func(_ *cluster.Settings) {})
   136  	})
   137  	t.Run("batch=1", func(t *testing.T) {
   138  		// The test normally doesn't trigger the batching behavior, so lower
   139  		// the threshold to force it.
   140  		init := func(st *cluster.Settings) {
   141  			importBatchSize.Override(&st.SV, 1)
   142  		}
   143  		runTestImport(t, init)
   144  	})
   145  }
   146  
   147  func runTestImport(t *testing.T, init func(*cluster.Settings)) {
   148  	defer leaktest.AfterTest(t)()
   149  
   150  	dir, dirCleanupFn := testutils.TempDir(t)
   151  	defer dirCleanupFn()
   152  
   153  	if err := os.Mkdir(filepath.Join(dir, "foo"), 0755); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  
   157  	const (
   158  		oldID   = 51
   159  		indexID = 1
   160  	)
   161  
   162  	srcPrefix := makeKeyRewriterPrefixIgnoringInterleaved(oldID, indexID)
   163  	var keys []roachpb.Key
   164  	for i := 0; i < 8; i++ {
   165  		key := append([]byte(nil), srcPrefix...)
   166  		key = encoding.EncodeStringAscending(key, fmt.Sprintf("k%d", i))
   167  		keys = append(keys, key)
   168  	}
   169  
   170  	writeSST := func(t *testing.T, offsets []int) string {
   171  		path := strconv.FormatInt(hlc.UnixNano(), 10)
   172  
   173  		sstFile := &storage.MemFile{}
   174  		sst := storage.MakeBackupSSTWriter(sstFile)
   175  		defer sst.Close()
   176  		ts := hlc.NewClock(hlc.UnixNano, time.Nanosecond).Now()
   177  		value := roachpb.MakeValueFromString("bar")
   178  		for _, idx := range offsets {
   179  			key := keys[idx]
   180  			value.ClearChecksum()
   181  			value.InitChecksum(key)
   182  			if err := sst.Put(storage.MVCCKey{Key: key, Timestamp: ts}, value.RawBytes); err != nil {
   183  				t.Fatalf("%+v", err)
   184  			}
   185  		}
   186  		if err := sst.Finish(); err != nil {
   187  			t.Fatalf("%+v", err)
   188  		}
   189  		if err := ioutil.WriteFile(filepath.Join(dir, "foo", path), sstFile.Data(), 0644); err != nil {
   190  			t.Fatalf("%+v", err)
   191  		}
   192  		return path
   193  	}
   194  
   195  	// Make the first few WriteBatch/AddSSTable calls return
   196  	// AmbiguousResultError. Import should be resilient to this.
   197  	const initialAmbiguousSubReqs = 3
   198  	remainingAmbiguousSubReqs := int64(initialAmbiguousSubReqs)
   199  	knobs := base.TestingKnobs{Store: &kvserver.StoreTestingKnobs{
   200  		EvalKnobs: kvserverbase.BatchEvalTestingKnobs{
   201  			TestingEvalFilter: func(filterArgs kvserverbase.FilterArgs) *roachpb.Error {
   202  				switch filterArgs.Req.(type) {
   203  				case *roachpb.WriteBatchRequest, *roachpb.AddSSTableRequest:
   204  				// No-op.
   205  				default:
   206  					return nil
   207  				}
   208  				r := atomic.AddInt64(&remainingAmbiguousSubReqs, -1)
   209  				if r < 0 {
   210  					return nil
   211  				}
   212  				return roachpb.NewError(roachpb.NewAmbiguousResultError(strconv.Itoa(int(r))))
   213  			},
   214  		},
   215  	}}
   216  
   217  	ctx := context.Background()
   218  	args := base.TestServerArgs{Knobs: knobs, ExternalIODir: dir}
   219  	// TODO(dan): This currently doesn't work with AddSSTable on in-memory
   220  	// stores because RocksDB's InMemoryEnv doesn't support NewRandomRWFile
   221  	// (which breaks the global-seqno rewrite used when the added sstable
   222  	// overlaps with existing data in the RocksDB instance). #16345.
   223  	args.StoreSpecs = []base.StoreSpec{{InMemory: false, Path: filepath.Join(dir, "testserver")}}
   224  	s, _, kvDB := serverutils.StartServer(t, args)
   225  	defer s.Stopper().Stop(ctx)
   226  	init(s.ClusterSettings())
   227  
   228  	storage, err := cloud.ExternalStorageConfFromURI("nodelocal://0/foo")
   229  	if err != nil {
   230  		t.Fatalf("%+v", err)
   231  	}
   232  
   233  	const splitKey1, splitKey2 = 3, 5
   234  	// Each test case consists of some number of batches of keys, represented as
   235  	// ints [0, 8). Splits are at 3 and 5.
   236  	for i, testCase := range [][][]int{
   237  		// Simple cases, no spanning splits, try first, last, middle, etc in each.
   238  		// r1
   239  		{{0}},
   240  		{{1}},
   241  		{{2}},
   242  		{{0, 1, 2}},
   243  		{{0}, {1}, {2}},
   244  
   245  		// r2
   246  		{{3}},
   247  		{{4}},
   248  		{{3, 4}},
   249  		{{3}, {4}},
   250  
   251  		// r3
   252  		{{5}},
   253  		{{5, 6, 7}},
   254  		{{6}},
   255  
   256  		// batches exactly matching spans.
   257  		{{0, 1, 2}, {3, 4}, {5, 6, 7}},
   258  
   259  		// every key, in its own batch.
   260  		{{0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}},
   261  
   262  		// every key in one big batch.
   263  		{{0, 1, 2, 3, 4, 5, 6, 7}},
   264  
   265  		// Look for off-by-ones on and around the splits.
   266  		{{2, 3}},
   267  		{{1, 3}},
   268  		{{2, 4}},
   269  		{{1, 4}},
   270  		{{1, 5}},
   271  		{{2, 5}},
   272  
   273  		// Mixture of split-aligned and non-aligned batches.
   274  		{{1}, {5}, {6}},
   275  		{{1, 2, 3}, {4, 5}, {6, 7}},
   276  		{{0}, {2, 3, 5}, {7}},
   277  		{{0, 4}, {5, 7}},
   278  		{{0, 3}, {4}},
   279  	} {
   280  		t.Run(fmt.Sprintf("%d-%v", i, testCase), func(t *testing.T) {
   281  			newID := sqlbase.ID(100 + i)
   282  			kr := prefixRewriter{{
   283  				OldPrefix: srcPrefix,
   284  				NewPrefix: makeKeyRewriterPrefixIgnoringInterleaved(newID, indexID),
   285  			}}
   286  			rekeys := []roachpb.ImportRequest_TableRekey{
   287  				{
   288  					OldID: oldID,
   289  					NewDesc: mustMarshalDesc(t, &sqlbase.TableDescriptor{
   290  						ID: newID,
   291  						PrimaryIndex: sqlbase.IndexDescriptor{
   292  							ID: indexID,
   293  						},
   294  					}),
   295  				},
   296  			}
   297  
   298  			first := keys[testCase[0][0]]
   299  			last := keys[testCase[len(testCase)-1][len(testCase[len(testCase)-1])-1]]
   300  
   301  			reqStartKey, ok := kr.rewriteKey(append([]byte(nil), keys[0]...))
   302  			if !ok {
   303  				t.Fatalf("failed to rewrite key: %s", reqStartKey)
   304  			}
   305  			reqEndKey, ok := kr.rewriteKey(append([]byte(nil), keys[len(keys)-1].PrefixEnd()...))
   306  			if !ok {
   307  				t.Fatalf("failed to rewrite key: %s", reqEndKey)
   308  			}
   309  			reqMidKey1, ok := kr.rewriteKey(append([]byte(nil), keys[splitKey1]...))
   310  			if !ok {
   311  				t.Fatalf("failed to rewrite key: %s", reqMidKey1)
   312  			}
   313  			reqMidKey2, ok := kr.rewriteKey(append([]byte(nil), keys[splitKey2]...))
   314  			if !ok {
   315  				t.Fatalf("failed to rewrite key: %s", reqMidKey2)
   316  			}
   317  
   318  			if err := kvDB.AdminSplit(ctx, reqMidKey1, reqMidKey1, hlc.MaxTimestamp /* expirationTime */); err != nil {
   319  				t.Fatal(err)
   320  			}
   321  			if err := kvDB.AdminSplit(ctx, reqMidKey2, reqMidKey2, hlc.MaxTimestamp /* expirationTime */); err != nil {
   322  				t.Fatal(err)
   323  			}
   324  
   325  			atomic.StoreInt64(&remainingAmbiguousSubReqs, initialAmbiguousSubReqs)
   326  
   327  			req := &roachpb.ImportRequest{
   328  				RequestHeader: roachpb.RequestHeader{Key: reqStartKey},
   329  				DataSpan:      roachpb.Span{Key: first, EndKey: last.PrefixEnd()},
   330  				Rekeys:        rekeys,
   331  			}
   332  
   333  			var slurp []string
   334  			for ks := range testCase {
   335  				f := writeSST(t, testCase[ks])
   336  				slurp = append(slurp, f)
   337  				req.Files = append(req.Files, roachpb.ImportRequest_File{Dir: storage, Path: f})
   338  			}
   339  			expectedKVs := slurpSSTablesLatestKey(t, filepath.Join(dir, "foo"), slurp, kr)
   340  
   341  			// Import may be retried by DistSender if it takes too long to return, so
   342  			// make sure it's idempotent.
   343  			for j := 0; j < 2; j++ {
   344  				b := &kv.Batch{}
   345  				b.AddRawRequest(req)
   346  				if err := kvDB.Run(ctx, b); err != nil {
   347  					t.Fatalf("%+v", err)
   348  				}
   349  				clientKVs, err := kvDB.Scan(ctx, reqStartKey, reqEndKey, 0)
   350  				if err != nil {
   351  					t.Fatalf("%+v", err)
   352  				}
   353  				kvs := clientKVsToEngineKVs(clientKVs)
   354  
   355  				if !reflect.DeepEqual(kvs, expectedKVs) {
   356  					for i := 0; i < len(kvs) || i < len(expectedKVs); i++ {
   357  						if i < len(expectedKVs) {
   358  							t.Logf("expected %d\t%v\t%v", i, expectedKVs[i].Key, expectedKVs[i].Value)
   359  						}
   360  						if i < len(kvs) {
   361  							t.Logf("got      %d\t%v\t%v", i, kvs[i].Key, kvs[i].Value)
   362  						}
   363  					}
   364  					t.Fatalf("got %+v expected %+v", kvs, expectedKVs)
   365  				}
   366  			}
   367  
   368  			if r := atomic.LoadInt64(&remainingAmbiguousSubReqs); r > 0 {
   369  				t.Errorf("expected ambiguous sub-requests to be depleted got %d", r)
   370  			}
   371  		})
   372  	}
   373  }