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

     1  // Copyright 2018 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 changefeedccl
    10  
    11  import (
    12  	"context"
    13  	gosql "database/sql"
    14  	gojson "encoding/json"
    15  	"fmt"
    16  	"net/url"
    17  	"reflect"
    18  	"sort"
    19  	"strings"
    20  	"testing"
    21  
    22  	"github.com/cockroachdb/apd"
    23  	"github.com/cockroachdb/cockroach/pkg/base"
    24  	"github.com/cockroachdb/cockroach/pkg/ccl/changefeedccl/cdctest"
    25  	"github.com/cockroachdb/cockroach/pkg/security"
    26  	"github.com/cockroachdb/cockroach/pkg/sql/execinfra"
    27  	"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
    28  	"github.com/cockroachdb/cockroach/pkg/testutils"
    29  	"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
    30  	"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
    31  	"github.com/cockroachdb/cockroach/pkg/util/hlc"
    32  	"github.com/cockroachdb/cockroach/pkg/util/log"
    33  )
    34  
    35  func waitForSchemaChange(
    36  	t testing.TB, sqlDB *sqlutils.SQLRunner, stmt string, arguments ...interface{},
    37  ) {
    38  	sqlDB.Exec(t, stmt, arguments...)
    39  	row := sqlDB.QueryRow(t, "SELECT job_id FROM [SHOW JOBS] ORDER BY created DESC LIMIT 1")
    40  	var jobID string
    41  	row.Scan(&jobID)
    42  
    43  	testutils.SucceedsSoon(t, func() error {
    44  		row := sqlDB.QueryRow(t, "SELECT status FROM [SHOW JOBS] WHERE job_id = $1", jobID)
    45  		var status string
    46  		row.Scan(&status)
    47  		if status != "succeeded" {
    48  			return fmt.Errorf("Job %s had status %s, wanted 'succeeded'", jobID, status)
    49  		}
    50  		return nil
    51  	})
    52  }
    53  
    54  func readNextMessages(t testing.TB, f cdctest.TestFeed, numMessages int, stripTs bool) []string {
    55  	t.Helper()
    56  
    57  	var actual []string
    58  	var value []byte
    59  	var message map[string]interface{}
    60  	for len(actual) < numMessages {
    61  		m, err := f.Next()
    62  		if log.V(1) {
    63  			log.Infof(context.Background(), `%v %s: %s->%s`, err, m.Topic, m.Key, m.Value)
    64  		}
    65  		if err != nil {
    66  			t.Fatal(err)
    67  		} else if m == nil {
    68  			t.Fatal(`expected message`)
    69  		} else if len(m.Key) > 0 || len(m.Value) > 0 {
    70  			if stripTs {
    71  				if err := gojson.Unmarshal(m.Value, &message); err != nil {
    72  					t.Fatalf(`%s: %s`, m.Value, err)
    73  				}
    74  				delete(message, "updated")
    75  				value, err = cdctest.ReformatJSON(message)
    76  				if err != nil {
    77  					t.Fatal(err)
    78  				}
    79  			} else {
    80  				value = m.Value
    81  			}
    82  			actual = append(actual, fmt.Sprintf(`%s: %s->%s`, m.Topic, m.Key, value))
    83  		}
    84  	}
    85  	return actual
    86  }
    87  
    88  func assertPayloadsBase(t testing.TB, f cdctest.TestFeed, expected []string, stripTs bool) {
    89  	t.Helper()
    90  	actual := readNextMessages(t, f, len(expected), stripTs)
    91  	sort.Strings(expected)
    92  	sort.Strings(actual)
    93  	if !reflect.DeepEqual(expected, actual) {
    94  		t.Fatalf("expected\n  %s\ngot\n  %s",
    95  			strings.Join(expected, "\n  "), strings.Join(actual, "\n  "))
    96  	}
    97  }
    98  
    99  func assertPayloads(t testing.TB, f cdctest.TestFeed, expected []string) {
   100  	t.Helper()
   101  	assertPayloadsBase(t, f, expected, false)
   102  }
   103  
   104  func assertPayloadsStripTs(t testing.TB, f cdctest.TestFeed, expected []string) {
   105  	t.Helper()
   106  	assertPayloadsBase(t, f, expected, true)
   107  }
   108  
   109  func avroToJSON(t testing.TB, reg *testSchemaRegistry, avroBytes []byte) []byte {
   110  	if len(avroBytes) == 0 {
   111  		return nil
   112  	}
   113  	native, err := reg.encodedAvroToNative(avroBytes)
   114  	if err != nil {
   115  		t.Fatal(err)
   116  	}
   117  	// The avro textual format is a more natural fit, but it's non-deterministic
   118  	// because of go's randomized map ordering. Instead, we use gojson.Marshal,
   119  	// which sorts its object keys and so is deterministic.
   120  	json, err := gojson.Marshal(native)
   121  	if err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	return json
   125  }
   126  
   127  func assertPayloadsAvro(
   128  	t testing.TB, reg *testSchemaRegistry, f cdctest.TestFeed, expected []string,
   129  ) {
   130  	t.Helper()
   131  
   132  	var actual []string
   133  	for len(actual) < len(expected) {
   134  		m, err := f.Next()
   135  		if err != nil {
   136  			t.Fatal(err)
   137  		} else if m == nil {
   138  			t.Fatal(`expected message`)
   139  		} else if m.Key != nil {
   140  			key, value := avroToJSON(t, reg, m.Key), avroToJSON(t, reg, m.Value)
   141  			actual = append(actual, fmt.Sprintf(`%s: %s->%s`, m.Topic, key, value))
   142  		}
   143  	}
   144  
   145  	// The tests that use this aren't concerned with order, just that these are
   146  	// the next len(expected) messages.
   147  	sort.Strings(expected)
   148  	sort.Strings(actual)
   149  	if !reflect.DeepEqual(expected, actual) {
   150  		t.Fatalf("expected\n  %s\ngot\n  %s",
   151  			strings.Join(expected, "\n  "), strings.Join(actual, "\n  "))
   152  	}
   153  }
   154  
   155  func parseTimeToHLC(t testing.TB, s string) hlc.Timestamp {
   156  	t.Helper()
   157  	d, _, err := apd.NewFromString(s)
   158  	if err != nil {
   159  		t.Fatal(err)
   160  	}
   161  	ts, err := tree.DecimalToHLC(d)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	return ts
   166  }
   167  
   168  func expectResolvedTimestamp(t testing.TB, f cdctest.TestFeed) hlc.Timestamp {
   169  	t.Helper()
   170  	m, err := f.Next()
   171  	if err != nil {
   172  		t.Fatal(err)
   173  	} else if m == nil {
   174  		t.Fatal(`expected message`)
   175  	}
   176  	if m.Key != nil {
   177  		t.Fatalf(`unexpected row %s: %s -> %s`, m.Topic, m.Key, m.Value)
   178  	}
   179  	if m.Resolved == nil {
   180  		t.Fatal(`expected a resolved timestamp notification`)
   181  	}
   182  
   183  	var resolvedRaw struct {
   184  		Resolved string `json:"resolved"`
   185  	}
   186  	if err := gojson.Unmarshal(m.Resolved, &resolvedRaw); err != nil {
   187  		t.Fatal(err)
   188  	}
   189  
   190  	return parseTimeToHLC(t, resolvedRaw.Resolved)
   191  }
   192  
   193  func expectResolvedTimestampAvro(
   194  	t testing.TB, reg *testSchemaRegistry, f cdctest.TestFeed,
   195  ) hlc.Timestamp {
   196  	t.Helper()
   197  	m, err := f.Next()
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	} else if m == nil {
   201  		t.Fatal(`expected message`)
   202  	}
   203  	if m.Key != nil {
   204  		key, value := avroToJSON(t, reg, m.Key), avroToJSON(t, reg, m.Value)
   205  		t.Fatalf(`unexpected row %s: %s -> %s`, m.Topic, key, value)
   206  	}
   207  	if m.Resolved == nil {
   208  		t.Fatal(`expected a resolved timestamp notification`)
   209  	}
   210  	resolvedNative, err := reg.encodedAvroToNative(m.Resolved)
   211  	if err != nil {
   212  		t.Fatal(err)
   213  	}
   214  	resolved := resolvedNative.(map[string]interface{})[`resolved`]
   215  	return parseTimeToHLC(t, resolved.(map[string]interface{})[`string`].(string))
   216  }
   217  
   218  func sinklessTest(testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory)) func(*testing.T) {
   219  	return func(t *testing.T) {
   220  		ctx := context.Background()
   221  		knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{}}}
   222  		s, db, _ := serverutils.StartServer(t, base.TestServerArgs{
   223  			Knobs:       knobs,
   224  			UseDatabase: `d`,
   225  		})
   226  		defer s.Stopper().Stop(ctx)
   227  		sqlDB := sqlutils.MakeSQLRunner(db)
   228  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`)
   229  		// TODO(dan): We currently have to set this to an extremely conservative
   230  		// value because otherwise schema changes become flaky (they don't commit
   231  		// their txn in time, get pushed by closed timestamps, and retry forever).
   232  		// This is more likely when the tests run slower (race builds or inside
   233  		// docker). The conservative value makes our tests take a lot longer,
   234  		// though. Figure out some way to speed this up.
   235  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`)
   236  		// TODO(dan): This is still needed to speed up table_history, that should be
   237  		// moved to RangeFeed as well.
   238  		sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`)
   239  		sqlDB.Exec(t, `CREATE DATABASE d`)
   240  
   241  		sink, cleanup := sqlutils.PGUrl(t, s.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
   242  		defer cleanup()
   243  		f := cdctest.MakeSinklessFeedFactory(s, sink)
   244  		testFn(t, db, f)
   245  	}
   246  }
   247  
   248  func enterpriseTest(testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory)) func(*testing.T) {
   249  	return enterpriseTestWithServerArgs(nil, testFn)
   250  }
   251  
   252  func enterpriseTestWithServerArgs(
   253  	argsFn func(args *base.TestServerArgs),
   254  	testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory),
   255  ) func(*testing.T) {
   256  	return func(t *testing.T) {
   257  		ctx := context.Background()
   258  
   259  		flushCh := make(chan struct{}, 1)
   260  		defer close(flushCh)
   261  		knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{
   262  			AfterSinkFlush: func() error {
   263  				select {
   264  				case flushCh <- struct{}{}:
   265  				default:
   266  				}
   267  				return nil
   268  			},
   269  		}}}
   270  		args := base.TestServerArgs{
   271  			UseDatabase: "d",
   272  			Knobs:       knobs,
   273  		}
   274  		if argsFn != nil {
   275  			argsFn(&args)
   276  		}
   277  		s, db, _ := serverutils.StartServer(t, args)
   278  		defer s.Stopper().Stop(ctx)
   279  		sqlDB := sqlutils.MakeSQLRunner(db)
   280  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`)
   281  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`)
   282  		sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`)
   283  		sqlDB.Exec(t, `CREATE DATABASE d`)
   284  		sink, cleanup := sqlutils.PGUrl(t, s.ServingSQLAddr(), t.Name(), url.User(security.RootUser))
   285  		defer cleanup()
   286  		f := cdctest.MakeTableFeedFactory(s, db, flushCh, sink)
   287  
   288  		testFn(t, db, f)
   289  	}
   290  }
   291  
   292  func cloudStorageTest(
   293  	testFn func(*testing.T, *gosql.DB, cdctest.TestFeedFactory),
   294  ) func(*testing.T) {
   295  	return func(t *testing.T) {
   296  		ctx := context.Background()
   297  
   298  		dir, dirCleanupFn := testutils.TempDir(t)
   299  		defer dirCleanupFn()
   300  
   301  		flushCh := make(chan struct{}, 1)
   302  		defer close(flushCh)
   303  		knobs := base.TestingKnobs{DistSQL: &execinfra.TestingKnobs{Changefeed: &TestingKnobs{
   304  			AfterSinkFlush: func() error {
   305  				select {
   306  				case flushCh <- struct{}{}:
   307  				default:
   308  				}
   309  				return nil
   310  			},
   311  		}}}
   312  
   313  		s, db, _ := serverutils.StartServer(t, base.TestServerArgs{
   314  			UseDatabase:   "d",
   315  			ExternalIODir: dir,
   316  			Knobs:         knobs,
   317  		})
   318  		defer s.Stopper().Stop(ctx)
   319  		sqlDB := sqlutils.MakeSQLRunner(db)
   320  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.rangefeed.enabled = true`)
   321  		sqlDB.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '1s'`)
   322  		sqlDB.Exec(t, `SET CLUSTER SETTING changefeed.experimental_poll_interval = '10ms'`)
   323  		sqlDB.Exec(t, `CREATE DATABASE d`)
   324  
   325  		f := cdctest.MakeCloudFeedFactory(s, db, dir, flushCh)
   326  		testFn(t, db, f)
   327  	}
   328  }
   329  
   330  func feed(
   331  	t testing.TB, f cdctest.TestFeedFactory, create string, args ...interface{},
   332  ) cdctest.TestFeed {
   333  	t.Helper()
   334  	feed, err := f.Feed(create, args...)
   335  	if err != nil {
   336  		t.Fatal(err)
   337  	}
   338  	return feed
   339  }
   340  
   341  func closeFeed(t testing.TB, f cdctest.TestFeed) {
   342  	t.Helper()
   343  	if err := f.Close(); err != nil {
   344  		t.Fatal(err)
   345  	}
   346  }
   347  
   348  func forceTableGC(
   349  	t testing.TB,
   350  	tsi serverutils.TestServerInterface,
   351  	sqlDB *sqlutils.SQLRunner,
   352  	database, table string,
   353  ) {
   354  	t.Helper()
   355  	if err := tsi.ForceTableGC(context.Background(), database, table, tsi.Clock().Now()); err != nil {
   356  		t.Fatal(err)
   357  	}
   358  }