diff --git a/pkg/ccl/spanconfigccl/spanconfigcomparedccl/BUILD.bazel b/pkg/ccl/spanconfigccl/spanconfigcomparedccl/BUILD.bazel index 3273ae485b7a..ac3a887115c6 100644 --- a/pkg/ccl/spanconfigccl/spanconfigcomparedccl/BUILD.bazel +++ b/pkg/ccl/spanconfigccl/spanconfigcomparedccl/BUILD.bazel @@ -13,6 +13,7 @@ go_test( "//pkg/ccl/utilccl", "//pkg/gossip", "//pkg/jobs", + "//pkg/kv/kvserver/protectedts", "//pkg/roachpb:with-mocks", "//pkg/security", "//pkg/security/securitytest", diff --git a/pkg/ccl/spanconfigccl/spanconfigcomparedccl/datadriven_test.go b/pkg/ccl/spanconfigccl/spanconfigcomparedccl/datadriven_test.go index f05e6b4c7bbb..bac9ac54fe98 100644 --- a/pkg/ccl/spanconfigccl/spanconfigcomparedccl/datadriven_test.go +++ b/pkg/ccl/spanconfigccl/spanconfigcomparedccl/datadriven_test.go @@ -22,6 +22,7 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl/kvccl/kvtenantccl" "github.com/cockroachdb/cockroach/pkg/gossip" "github.com/cockroachdb/cockroach/pkg/jobs" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/spanconfig" "github.com/cockroachdb/cockroach/pkg/spanconfig/spanconfigtestutils" @@ -109,7 +110,7 @@ func TestDataDriven(t *testing.T) { tdb.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '100ms'`) } - spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs) + spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs, &protectedts.TestingKnobs{}) defer spanConfigTestCluster.Cleanup() kvSubscriber := tc.Server(0).SpanConfigKVSubscriber().(spanconfig.KVSubscriber) diff --git a/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/BUILD.bazel b/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/BUILD.bazel index 3bb5c6d2d1e0..d486631f5784 100644 --- a/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/BUILD.bazel +++ b/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/BUILD.bazel @@ -14,6 +14,7 @@ go_test( "//pkg/ccl/utilccl", "//pkg/jobs", "//pkg/keys", + "//pkg/kv/kvserver/protectedts", "//pkg/roachpb:with-mocks", "//pkg/security", "//pkg/security/securitytest", diff --git a/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/datadriven_test.go b/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/datadriven_test.go index 10632465338d..964c8ce5ff02 100644 --- a/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/datadriven_test.go +++ b/pkg/ccl/spanconfigccl/spanconfigreconcilerccl/datadriven_test.go @@ -20,6 +20,7 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl" "github.com/cockroachdb/cockroach/pkg/jobs" "github.com/cockroachdb/cockroach/pkg/keys" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/spanconfig" "github.com/cockroachdb/cockroach/pkg/spanconfig/spanconfigtestutils" @@ -102,7 +103,7 @@ func TestDataDriven(t *testing.T) { tdb.Exec(t, `SET CLUSTER SETTING kv.closed_timestamp.target_duration = '100ms'`) } - spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs) + spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs, &protectedts.TestingKnobs{}) defer spanConfigTestCluster.Cleanup() systemTenant := spanConfigTestCluster.InitializeTenant(ctx, roachpb.SystemTenantID) diff --git a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/BUILD.bazel b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/BUILD.bazel index d8adfb8da891..379ac3ca827a 100644 --- a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/BUILD.bazel +++ b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/BUILD.bazel @@ -13,6 +13,11 @@ go_test( "//pkg/ccl/partitionccl", "//pkg/ccl/utilccl", "//pkg/config/zonepb", + "//pkg/jobs", + "//pkg/jobs/jobsprotectedts", + "//pkg/kv", + "//pkg/kv/kvserver/protectedts", + "//pkg/kv/kvserver/protectedts/ptpb", "//pkg/roachpb:with-mocks", "//pkg/security", "//pkg/security/securitytest", @@ -20,15 +25,19 @@ go_test( "//pkg/spanconfig", "//pkg/spanconfig/spanconfigtestutils", "//pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster", + "//pkg/sql", "//pkg/sql/catalog/descpb", "//pkg/sql/catalog/tabledesc", + "//pkg/sql/distsql", "//pkg/testutils", "//pkg/testutils/serverutils", "//pkg/testutils/sqlutils", "//pkg/testutils/testcluster", + "//pkg/util/hlc", "//pkg/util/leaktest", "//pkg/util/log", "//pkg/util/randutil", + "//pkg/util/uuid", "@com_github_cockroachdb_datadriven//:datadriven", "@com_github_stretchr_testify//require", ], diff --git a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/datadriven_test.go b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/datadriven_test.go index 705f8bf9affa..b2b24756bc3a 100644 --- a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/datadriven_test.go +++ b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/datadriven_test.go @@ -19,17 +19,26 @@ import ( _ "github.com/cockroachdb/cockroach/pkg/ccl/kvccl/kvtenantccl" _ "github.com/cockroachdb/cockroach/pkg/ccl/partitionccl" "github.com/cockroachdb/cockroach/pkg/config/zonepb" + "github.com/cockroachdb/cockroach/pkg/jobs" + "github.com/cockroachdb/cockroach/pkg/jobs/jobsprotectedts" + "github.com/cockroachdb/cockroach/pkg/kv" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/spanconfig" "github.com/cockroachdb/cockroach/pkg/spanconfig/spanconfigtestutils" "github.com/cockroachdb/cockroach/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster" + "github.com/cockroachdb/cockroach/pkg/sql" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/catalog/tabledesc" + "github.com/cockroachdb/cockroach/pkg/sql/distsql" "github.com/cockroachdb/cockroach/pkg/testutils" "github.com/cockroachdb/cockroach/pkg/testutils/sqlutils" "github.com/cockroachdb/cockroach/pkg/testutils/testcluster" + "github.com/cockroachdb/cockroach/pkg/util/hlc" "github.com/cockroachdb/cockroach/pkg/util/leaktest" "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/cockroachdb/cockroach/pkg/util/uuid" "github.com/cockroachdb/datadriven" "github.com/stretchr/testify/require" ) @@ -61,6 +70,15 @@ import ( // - "mark-table-public" [database=] [table=] // Marks the given table as public. // +// - "protect" [id=] [ts=] +// cluster OR +// tenants id1,id2... OR +// schemaObject id1,id2... +// Creates and writes a protected timestamp record with id and ts with an +// appropriate ptpb.Target. +// +// - "release" [id=] +// Releases the protected timestamp record with id. func TestDataDriven(t *testing.T) { defer leaktest.AfterTest(t)() defer log.Scope(t).Close(t) @@ -77,17 +95,21 @@ func TestDataDriven(t *testing.T) { // test cluster). ManagerDisableJobCreation: true, } + ptsKnobs := &protectedts.TestingKnobs{ + EnableProtectedTimestampForMultiTenant: true, + } datadriven.Walk(t, testutils.TestDataPath(t), func(t *testing.T, path string) { tc := testcluster.StartTestCluster(t, 1, base.TestClusterArgs{ ServerArgs: base.TestServerArgs{ Knobs: base.TestingKnobs{ - SpanConfig: scKnobs, + SpanConfig: scKnobs, + ProtectedTS: ptsKnobs, }, }, }) defer tc.Stopper().Stop(ctx) - spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs) + spanConfigTestCluster := spanconfigtestcluster.NewHandle(t, tc, scKnobs, ptsKnobs) defer spanConfigTestCluster.Cleanup() var tenant *spanconfigtestcluster.Tenant @@ -98,6 +120,29 @@ func TestDataDriven(t *testing.T) { tenant = spanConfigTestCluster.InitializeTenant(ctx, roachpb.SystemTenantID) } + execCfg := tenant.ExecutorConfig().(sql.ExecutorConfig) + ptp := tenant.DistSQLServer().(*distsql.ServerImpl).ServerConfig.ProtectedTimestampProvider + jr := tenant.JobRegistry().(*jobs.Registry) + + mkRecordAndProtect := func(recordID string, ts hlc.Timestamp, target *ptpb.Target) { + jobID := jr.MakeJobID() + require.NoError(t, execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) (err error) { + recID, err := uuid.FromBytes([]byte(strings.Repeat(recordID, 16))) + require.NoError(t, err) + rec := jobsprotectedts.MakeRecord(recID, int64(jobID), ts, + nil /* deprecatedSpans */, jobsprotectedts.Jobs, target) + return ptp.Protect(ctx, txn, rec) + })) + } + + releaseRecord := func(recordID string) { + require.NoError(t, execCfg.DB.Txn(ctx, func(ctx context.Context, txn *kv.Txn) error { + recID, err := uuid.FromBytes([]byte(strings.Repeat(recordID, 16))) + require.NoError(t, err) + return ptp.Release(ctx, txn, recID) + })) + } + datadriven.RunTest(t, path, func(t *testing.T, d *datadriven.TestData) string { switch d.Cmd { case "exec-sql": @@ -182,7 +227,18 @@ func TestDataDriven(t *testing.T) { tenant.WithMutableTableDescriptor(ctx, dbName, tbName, func(mutable *tabledesc.Mutable) { mutable.SetPublic() }) - + case "protect": + var recordID string + var protectTS int + d.ScanArgs(t, "id", &recordID) + d.ScanArgs(t, "ts", &protectTS) + target := spanconfigtestutils.ParseProtectionTarget(t, d.Input) + mkRecordAndProtect(recordID, hlc.Timestamp{WallTime: int64(protectTS)}, target) + return "" + case "release": + var recordID string + d.ScanArgs(t, "id", &recordID) + releaseRecord(recordID) default: t.Fatalf("unknown command: %s", d.Cmd) } diff --git a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/protectedts b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/protectedts new file mode 100644 index 000000000000..e5b928b1f0c0 --- /dev/null +++ b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/protectedts @@ -0,0 +1,76 @@ +# Create a database with some tables and write protected timestamps on the +# tables and database. Check that span configurations are as we expect. + +exec-sql +CREATE DATABASE db; +CREATE TABLE db.t1(id INT); +CREATE TABLE db.t2(); +---- + +# Scheme object IDs +# db: 54 +# t1: 56 +# t2: 57 + +# Alter zone config fields on the database and one of the tables to ensure +# things are cascading. +exec-sql +ALTER DATABASE db CONFIGURE ZONE USING num_replicas=7; +ALTER TABLE db.t1 CONFIGURE ZONE USING num_voters=5; +---- + +# Write a protected timestamp on t1. +protect id=1 ts=1 +schemaObject 56 +---- + +translate database=db +---- +/Table/5{6-7} num_replicas=7 num_voters=5 pts=[1] +/Table/5{7-8} num_replicas=7 + +# Write a protected timestamp on db, so we should see it on both t1 and t2. +protect id=2 ts=2 +schemaObject 54 +---- + +translate database=db +---- +/Table/5{6-7} num_replicas=7 num_voters=5 pts=[1 2] +/Table/5{7-8} num_replicas=7 pts=[2] + +# Release the protected timestamp on table t1 +release id=1 +---- + +translate database=db +---- +/Table/5{6-7} num_replicas=7 num_voters=5 pts=[2] +/Table/5{7-8} num_replicas=7 pts=[2] + +# Release the protected timestamp on database db +release id=2 +---- + +translate database=db +---- +/Table/5{6-7} num_replicas=7 num_voters=5 +/Table/5{7-8} num_replicas=7 + +# Create an index on t1 to ensure that subzones also see protected timestamps. +exec-sql +CREATE INDEX idx ON db.t1(id); +ALTER INDEX db.t1@idx CONFIGURE ZONE USING gc.ttlseconds = 1; +---- + +protect id=3 ts=3 +schemaObject 56 +---- + +translate database=db +---- +/Table/56{-/2} num_replicas=7 num_voters=5 pts=[3] +/Table/56/{2-3} ttl_seconds=1 num_replicas=7 num_voters=5 pts=[3] +/Table/5{6/3-7} num_replicas=7 num_voters=5 pts=[3] +/Table/5{7-8} num_replicas=7 + diff --git a/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/tenant/protectedts b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/tenant/protectedts new file mode 100644 index 000000000000..a0ccbf02e0a4 --- /dev/null +++ b/pkg/ccl/spanconfigccl/spanconfigsqltranslatorccl/testdata/tenant/protectedts @@ -0,0 +1,75 @@ +# Create a database with some tables and write protected timestamps on the +# tables and database. Check that span configurations are as we expect. + +exec-sql +CREATE DATABASE db; +CREATE TABLE db.t1(id INT); +CREATE TABLE db.t2(); +---- + +# Scheme object IDs +# db: 54 +# t1: 56 +# t2: 57 + +# Alter zone config fields on the database and one of the tables to ensure +# things are cascading. +exec-sql +ALTER DATABASE db CONFIGURE ZONE USING num_replicas=7; +ALTER TABLE db.t1 CONFIGURE ZONE USING num_voters=5; +---- + +# Write a protected timestamp on t1. +protect id=1 ts=1 +schemaObject 56 +---- + +translate database=db +---- +/Tenant/10/Table/5{6-7} num_replicas=7 num_voters=5 pts=[1] +/Tenant/10/Table/5{7-8} num_replicas=7 + +# Write a protected timestamp on db, so we should see it on both t1 and t2. +protect id=2 ts=2 +schemaObject 54 +---- + +translate database=db +---- +/Tenant/10/Table/5{6-7} num_replicas=7 num_voters=5 pts=[1 2] +/Tenant/10/Table/5{7-8} num_replicas=7 pts=[2] + +# Release the protected timestamp on table t1 +release id=1 +---- + +translate database=db +---- +/Tenant/10/Table/5{6-7} num_replicas=7 num_voters=5 pts=[2] +/Tenant/10/Table/5{7-8} num_replicas=7 pts=[2] + +# Release the protected timestamp on database db +release id=2 +---- + +translate database=db +---- +/Tenant/10/Table/5{6-7} num_replicas=7 num_voters=5 +/Tenant/10/Table/5{7-8} num_replicas=7 + +# Create an index on t1 to ensure that subzones also see protected timestamps. +exec-sql +CREATE INDEX idx ON db.t1(id); +ALTER INDEX db.t1@idx CONFIGURE ZONE USING gc.ttlseconds = 1; +---- + +protect id=3 ts=3 +schemaObject 56 +---- + +translate database=db +---- +/Tenant/10/Table/56{-/2} num_replicas=7 num_voters=5 pts=[3] +/Tenant/10/Table/56/{2-3} ttl_seconds=1 num_replicas=7 num_voters=5 pts=[3] +/Tenant/10/Table/5{6/3-7} num_replicas=7 num_voters=5 pts=[3] +/Tenant/10/Table/5{7-8} num_replicas=7 diff --git a/pkg/clusterversion/cockroach_versions.go b/pkg/clusterversion/cockroach_versions.go index 91d7647ccf7b..958bf1b6428c 100644 --- a/pkg/clusterversion/cockroach_versions.go +++ b/pkg/clusterversion/cockroach_versions.go @@ -225,10 +225,6 @@ const ( SeedTenantSpanConfigs // PublicSchemasWithDescriptors backs public schemas with descriptors. PublicSchemasWithDescriptors - // AlterSystemProtectedTimestampAddColumn adds a target column to the - // system.protected_ts_records table that describes what is protected by the - // record. - AlterSystemProtectedTimestampAddColumn // EnsureSpanConfigReconciliation ensures that the host tenant has run its // reconciliation process at least once. EnsureSpanConfigReconciliation @@ -239,6 +235,13 @@ const ( // EnableSpanConfigStore enables the use of the span configs infrastructure // in KV. EnableSpanConfigStore + // AlterSystemProtectedTimestampAddColumn adds a target column to the + // system.protected_ts_records table that describes what is protected by the + // record. + AlterSystemProtectedTimestampAddColumn + // EnableProtectedTimestampsForTenant enables the use of protected timestamps + // in secondary tenants. + EnableProtectedTimestampsForTenant // ************************************************* // Step (1): Add new versions here. @@ -344,21 +347,25 @@ var versionsSingleton = keyedVersions{ Version: roachpb.Version{Major: 21, Minor: 2, Internal: 34}, }, { - Key: AlterSystemProtectedTimestampAddColumn, + Key: EnsureSpanConfigReconciliation, Version: roachpb.Version{Major: 21, Minor: 2, Internal: 36}, }, { - Key: EnsureSpanConfigReconciliation, + Key: EnsureSpanConfigSubscription, Version: roachpb.Version{Major: 21, Minor: 2, Internal: 38}, }, { - Key: EnsureSpanConfigSubscription, + Key: EnableSpanConfigStore, Version: roachpb.Version{Major: 21, Minor: 2, Internal: 40}, }, { - Key: EnableSpanConfigStore, + Key: AlterSystemProtectedTimestampAddColumn, Version: roachpb.Version{Major: 21, Minor: 2, Internal: 42}, }, + { + Key: EnableProtectedTimestampsForTenant, + Version: roachpb.Version{Major: 21, Minor: 2, Internal: 44}, + }, // ************************************************* // Step (2): Add new versions here. diff --git a/pkg/clusterversion/key_string.go b/pkg/clusterversion/key_string.go index 881b8600e993..5dc70d988ffe 100644 --- a/pkg/clusterversion/key_string.go +++ b/pkg/clusterversion/key_string.go @@ -26,15 +26,16 @@ func _() { _ = x[PreSeedTenantSpanConfigs-15] _ = x[SeedTenantSpanConfigs-16] _ = x[PublicSchemasWithDescriptors-17] - _ = x[AlterSystemProtectedTimestampAddColumn-18] - _ = x[EnsureSpanConfigReconciliation-19] - _ = x[EnsureSpanConfigSubscription-20] - _ = x[EnableSpanConfigStore-21] + _ = x[EnsureSpanConfigReconciliation-18] + _ = x[EnsureSpanConfigSubscription-19] + _ = x[EnableSpanConfigStore-20] + _ = x[AlterSystemProtectedTimestampAddColumn-21] + _ = x[EnableProtectedTimestampsForTenant-22] } -const _Key_name = "V21_2Start22_1TargetBytesAvoidExcessAvoidDrainingNamesDrainingNamesMigrationTraceIDDoesntImplyStructuredRecordingAlterSystemTableStatisticsAddAvgSizeColAlterSystemStmtDiagReqsMVCCAddSSTableInsertPublicSchemaNamespaceEntryOnRestoreUnsplitRangesInAsyncGCJobsValidateGrantOptionPebbleFormatBlockPropertyCollectorProbeRequestSelectRPCsTakeTracingInfoInbandPreSeedTenantSpanConfigsSeedTenantSpanConfigsPublicSchemasWithDescriptorsAlterSystemProtectedTimestampAddColumnEnsureSpanConfigReconciliationEnsureSpanConfigSubscriptionEnableSpanConfigStore" +const _Key_name = "V21_2Start22_1TargetBytesAvoidExcessAvoidDrainingNamesDrainingNamesMigrationTraceIDDoesntImplyStructuredRecordingAlterSystemTableStatisticsAddAvgSizeColAlterSystemStmtDiagReqsMVCCAddSSTableInsertPublicSchemaNamespaceEntryOnRestoreUnsplitRangesInAsyncGCJobsValidateGrantOptionPebbleFormatBlockPropertyCollectorProbeRequestSelectRPCsTakeTracingInfoInbandPreSeedTenantSpanConfigsSeedTenantSpanConfigsPublicSchemasWithDescriptorsEnsureSpanConfigReconciliationEnsureSpanConfigSubscriptionEnableSpanConfigStoreAlterSystemProtectedTimestampAddColumnEnableProtectedTimestampsForTenant" -var _Key_index = [...]uint16{0, 5, 14, 36, 54, 76, 113, 152, 175, 189, 230, 256, 275, 309, 321, 352, 376, 397, 425, 463, 493, 521, 542} +var _Key_index = [...]uint16{0, 5, 14, 36, 54, 76, 113, 152, 175, 189, 230, 256, 275, 309, 321, 352, 376, 397, 425, 455, 483, 504, 542, 576} func (i Key) String() string { if i < 0 || i >= Key(len(_Key_index)-1) { diff --git a/pkg/roachpb/span_config.proto b/pkg/roachpb/span_config.proto index 762a01887fae..61e220b2de45 100644 --- a/pkg/roachpb/span_config.proto +++ b/pkg/roachpb/span_config.proto @@ -14,6 +14,7 @@ option go_package = "roachpb"; import "roachpb/data.proto"; import "gogoproto/gogo.proto"; +import "util/hlc/timestamp.proto"; // TODO(irfansharif): We could have the proto definitions in pkg/config/zonepb // use these messages instead of duplicating everything. @@ -140,6 +141,11 @@ message SpanConfig { // preferred option to least. The first preference that an existing replica of // a range matches will take priority for the lease. repeated LeasePreference lease_preferences = 9 [(gogoproto.nullable) = false]; + + // ProtectedTimestamps captures all the protected timestamp records that apply + // to the range. The timestamp values represent the timestamps after which + // data will be protected from GC, if the protection succeeds. + repeated util.hlc.Timestamp protected_timestamps = 10 [(gogoproto.nullable) = false]; } // SpanConfigEntry ties a span to its corresponding config. diff --git a/pkg/server/tenant.go b/pkg/server/tenant.go index c74b18a3913b..d3a0378f66a1 100644 --- a/pkg/server/tenant.go +++ b/pkg/server/tenant.go @@ -460,7 +460,7 @@ func makeTenantSQLServerArgs( if err != nil { panic(err) } - protectedTSProvider = dummyProtectedTSProvider{pp} + protectedTSProvider = tenantProtectedTSProvider{Provider: pp, st: st} } recorder := status.NewMetricsRecorder(clock, nil, rpcContext, nil, st) diff --git a/pkg/server/testserver.go b/pkg/server/testserver.go index 0a142f883316..fce9be786752 100644 --- a/pkg/server/testserver.go +++ b/pkg/server/testserver.go @@ -28,6 +28,7 @@ import ( "github.com/cenkalti/backoff" circuit "github.com/cockroachdb/circuitbreaker" "github.com/cockroachdb/cockroach/pkg/base" + "github.com/cockroachdb/cockroach/pkg/clusterversion" "github.com/cockroachdb/cockroach/pkg/config" "github.com/cockroachdb/cockroach/pkg/config/zonepb" "github.com/cockroachdb/cockroach/pkg/gossip" @@ -500,11 +501,17 @@ func (ts *TestServer) Start(ctx context.Context) error { return ts.Server.Start(ctx) } -type dummyProtectedTSProvider struct { +type tenantProtectedTSProvider struct { protectedts.Provider + st *cluster.Settings } -func (d dummyProtectedTSProvider) Protect(context.Context, *kv.Txn, *ptpb.Record) error { +func (d tenantProtectedTSProvider) Protect( + ctx context.Context, txn *kv.Txn, rec *ptpb.Record, +) error { + if d.st.Version.IsActive(ctx, clusterversion.EnableProtectedTimestampsForTenant) { + return d.Provider.Protect(ctx, txn, rec) + } return errors.New("fake protectedts.Provider") } diff --git a/pkg/spanconfig/spanconfigsqltranslator/sqltranslator.go b/pkg/spanconfig/spanconfigsqltranslator/sqltranslator.go index 392a7060361d..382dee2da1b2 100644 --- a/pkg/spanconfig/spanconfigsqltranslator/sqltranslator.go +++ b/pkg/spanconfig/spanconfigsqltranslator/sqltranslator.go @@ -69,6 +69,17 @@ func (s *SQLTranslator) Translate( // attempts. entries = entries[:0] + // Construct an in-memory view of the system.protected_ts_records table to + // populate the protected timestamp field on the emitted span configs. + ptsState, err := s.execCfg.ProtectedTimestampProvider.GetState(ctx, txn) + if err != nil { + return errors.Wrap(err, "failed to get protected timestamp state") + } + ptsStateReader, err := newProtectedTimestampStateReader(ctx, ptsState) + if err != nil { + return errors.Wrap(err, "failed to initialize ProtectedTimestampStateReader") + } + // For every ID we want to translate, first expand it to descendant leaf // IDs that have span configurations associated for them. We also // de-duplicate leaf IDs to not generate redundant entries. @@ -103,7 +114,7 @@ func (s *SQLTranslator) Translate( // For every unique leaf ID, generate span configurations. for _, leafID := range leafIDs { - translatedEntries, err := s.generateSpanConfigurations(ctx, leafID, txn, descsCol) + translatedEntries, err := s.generateSpanConfigurations(ctx, leafID, txn, descsCol, ptsStateReader) if err != nil { return err } @@ -134,7 +145,11 @@ var descLookupFlags = tree.CommonLookupFlags{ // ID. The ID must belong to an object that has a span configuration associated // with it, i.e, it should either belong to a table or a named zone. func (s *SQLTranslator) generateSpanConfigurations( - ctx context.Context, id descpb.ID, txn *kv.Txn, descsCol *descs.Collection, + ctx context.Context, + id descpb.ID, + txn *kv.Txn, + descsCol *descs.Collection, + ptsStateReader *protectedTimestampStateReader, ) (entries []roachpb.SpanConfigEntry, err error) { if zonepb.IsNamedZoneID(id) { return s.generateSpanConfigurationsForNamedZone(ctx, txn, id) @@ -158,7 +173,7 @@ func (s *SQLTranslator) generateSpanConfigurations( ) } - return s.generateSpanConfigurationsForTable(ctx, txn, desc) + return s.generateSpanConfigurationsForTable(ctx, txn, desc, ptsStateReader) } // generateSpanConfigurationsForNamedZone expects an ID corresponding to a named @@ -222,11 +237,55 @@ func (s *SQLTranslator) generateSpanConfigurationsForNamedZone( return entries, nil } +// setProtectedTimestampsForTable aggregates the protected timestamps that apply +// to the table and sets the field in the table's generated span configs. +// This includes protected timestamps that apply to the table, and its parent +// database. +// +// This method mutates the passed in `entries`. +func setProtectedTimestampsForTable( + entries *[]roachpb.SpanConfigEntry, + desc catalog.Descriptor, + ptsStateReader *protectedTimestampStateReader, +) { + protectedTimestamps := make([]hlc.Timestamp, 0) + // Get protections that apply directly to the table. + protectedTimestamps = append(protectedTimestamps, + ptsStateReader.GetProtectedTimestampsForSchemaObject(desc.GetID())...) + + // Get protections that apply to the database. + protectedTimestamps = append(protectedTimestamps, + ptsStateReader.GetProtectedTimestampsForSchemaObject(desc.GetParentID())...) + + if len(protectedTimestamps) == 0 { + return + } + + for i := range *entries { + (*entries)[i].Config.ProtectedTimestamps = protectedTimestamps + } +} + +// hydrateSpanConfigurationsForTable hydrates fields in a table's span +// configurations that are not derived from the table's zone configuration. +// +// This method mutates the passed in `entries`. +func hydrateSpanConfigurationsForTable( + entries *[]roachpb.SpanConfigEntry, + desc catalog.Descriptor, + ptsStateReader *protectedTimestampStateReader, +) { + setProtectedTimestampsForTable(entries, desc, ptsStateReader) +} + // generateSpanConfigurationsForTable generates the span configurations // corresponding to the given tableID. It uses a transactional view of // system.zones and system.descriptors to do so. func (s *SQLTranslator) generateSpanConfigurationsForTable( - ctx context.Context, txn *kv.Txn, desc catalog.Descriptor, + ctx context.Context, + txn *kv.Txn, + desc catalog.Descriptor, + ptsStateReader *protectedTimestampStateReader, ) ([]roachpb.SpanConfigEntry, error) { if desc.DescriptorType() != catalog.Table { return nil, errors.AssertionFailedf( @@ -279,6 +338,7 @@ func (s *SQLTranslator) generateSpanConfigurationsForTable( }) } + hydrateSpanConfigurationsForTable(&entries, desc, ptsStateReader) return entries, nil // TODO(irfansharif): There's an attack vector here that we haven't @@ -363,6 +423,7 @@ func (s *SQLTranslator) generateSpanConfigurationsForTable( }, ) } + hydrateSpanConfigurationsForTable(&entries, desc, ptsStateReader) return entries, nil } diff --git a/pkg/spanconfig/spanconfigtestutils/BUILD.bazel b/pkg/spanconfig/spanconfigtestutils/BUILD.bazel index 4a439f7753a5..aae898a950fb 100644 --- a/pkg/spanconfig/spanconfigtestutils/BUILD.bazel +++ b/pkg/spanconfig/spanconfigtestutils/BUILD.bazel @@ -10,8 +10,10 @@ go_library( visibility = ["//visibility:public"], deps = [ "//pkg/kv", + "//pkg/kv/kvserver/protectedts/ptpb", "//pkg/roachpb:with-mocks", "//pkg/spanconfig", + "//pkg/sql/catalog/descpb", "//pkg/util/syncutil", "@com_github_cockroachdb_datadriven//:datadriven", "@com_github_stretchr_testify//require", diff --git a/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/BUILD.bazel b/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/BUILD.bazel index 2f0b2744a245..2d40be7584c5 100644 --- a/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/BUILD.bazel +++ b/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/BUILD.bazel @@ -11,6 +11,7 @@ go_library( deps = [ "//pkg/base", "//pkg/kv", + "//pkg/kv/kvserver/protectedts", "//pkg/roachpb:with-mocks", "//pkg/security", "//pkg/spanconfig", diff --git a/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/cluster.go b/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/cluster.go index bca31c7371f4..45d2dedc632b 100644 --- a/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/cluster.go +++ b/pkg/spanconfig/spanconfigtestutils/spanconfigtestcluster/cluster.go @@ -17,6 +17,7 @@ import ( "testing" "github.com/cockroachdb/cockroach/pkg/base" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/security" "github.com/cockroachdb/cockroach/pkg/spanconfig" @@ -32,21 +33,26 @@ import ( // cluster while providing convenient, scoped access to each tenant's specific // span config primitives. It's not safe for concurrent use. type Handle struct { - t *testing.T - tc *testcluster.TestCluster - ts map[roachpb.TenantID]*Tenant - scKnobs *spanconfig.TestingKnobs + t *testing.T + tc *testcluster.TestCluster + ts map[roachpb.TenantID]*Tenant + scKnobs *spanconfig.TestingKnobs + ptsKnobs *protectedts.TestingKnobs } // NewHandle returns a new Handle. func NewHandle( - t *testing.T, tc *testcluster.TestCluster, scKnobs *spanconfig.TestingKnobs, + t *testing.T, + tc *testcluster.TestCluster, + scKnobs *spanconfig.TestingKnobs, + ptsKnobs *protectedts.TestingKnobs, ) *Handle { return &Handle{ - t: t, - tc: tc, - ts: make(map[roachpb.TenantID]*Tenant), - scKnobs: scKnobs, + t: t, + tc: tc, + ts: make(map[roachpb.TenantID]*Tenant), + scKnobs: scKnobs, + ptsKnobs: ptsKnobs, } } @@ -63,7 +69,8 @@ func (h *Handle) InitializeTenant(ctx context.Context, tenID roachpb.TenantID) * tenantArgs := base.TestTenantArgs{ TenantID: tenID, TestingKnobs: base.TestingKnobs{ - SpanConfig: h.scKnobs, + SpanConfig: h.scKnobs, + ProtectedTS: h.ptsKnobs, }, } var err error diff --git a/pkg/spanconfig/spanconfigtestutils/utils.go b/pkg/spanconfig/spanconfigtestutils/utils.go index 4afe6d7c5a2a..0f5914ed6fa3 100644 --- a/pkg/spanconfig/spanconfigtestutils/utils.go +++ b/pkg/spanconfig/spanconfigtestutils/utils.go @@ -15,11 +15,15 @@ import ( "fmt" "reflect" "regexp" + "sort" + "strconv" "strings" "testing" + "github.com/cockroachdb/cockroach/pkg/kv/kvserver/protectedts/ptpb" "github.com/cockroachdb/cockroach/pkg/roachpb" "github.com/cockroachdb/cockroach/pkg/spanconfig" + "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/datadriven" "github.com/stretchr/testify/require" ) @@ -236,6 +240,19 @@ func PrintSpanConfigDiffedAgainstDefaults(conf roachpb.SpanConfig) string { if !reflect.DeepEqual(conf.LeasePreferences, defaultConf.LeasePreferences) { diffs = append(diffs, fmt.Sprintf("lease_preferences=%v", conf.VoterConstraints)) } + if !reflect.DeepEqual(conf.ProtectedTimestamps, defaultConf.ProtectedTimestamps) { + sort.Slice(conf.ProtectedTimestamps, func(i, j int) bool { + return conf.ProtectedTimestamps[i].Less(conf.ProtectedTimestamps[j]) + }) + var s string + for i, pts := range conf.ProtectedTimestamps { + s += strconv.Itoa(int(pts.WallTime)) + if i != len(conf.ProtectedTimestamps)-1 { + s += " " + } + } + diffs = append(diffs, fmt.Sprintf("pts=[%s]", s)) + } return strings.Join(diffs, " ") } @@ -324,3 +341,45 @@ func GetSplitPoints(ctx context.Context, t *testing.T, reader spanconfig.StoreRe return splitPoints } + +// ParseProtectionTarget returns a ptpb.Target based on the input. This target +// could either refer to a Cluster, list of Tenants or SchemaObjects. +func ParseProtectionTarget(t *testing.T, input string) *ptpb.Target { + line := strings.Split(input, "\n") + if len(line) != 1 { + t.Fatal("only one target must be specified per protectedts operation") + } + target := line[0] + + const clusterPrefix, tenantPrefix, schemaObjectPrefix = "cluster", "tenant", "schemaObject" + switch { + case strings.HasPrefix(target, clusterPrefix): + return ptpb.MakeClusterTarget() + case strings.HasPrefix(target, tenantPrefix): + target = strings.TrimPrefix(target, target[:len(tenantPrefix)+1]) + tenantIDs := strings.Split(target, ",") + ids := make([]roachpb.TenantID, 0, len(tenantIDs)) + for _, tenID := range tenantIDs { + id, err := strconv.Atoi(tenID) + require.NoError(t, err) + ids = append(ids, roachpb.MakeTenantID(uint64(id))) + } + return ptpb.MakeTenantsTarget(ids) + case strings.HasPrefix(target, schemaObjectPrefix): + target = strings.TrimPrefix(target, target[:len(schemaObjectPrefix)+1]) + fmt.Println(target) + schemaObjectIDs := strings.Split(target, ",") + fmt.Println(schemaObjectIDs) + ids := make([]descpb.ID, 0, len(schemaObjectIDs)) + for _, tenID := range schemaObjectIDs { + id, err := strconv.Atoi(tenID) + require.NoError(t, err) + ids = append(ids, descpb.ID(id)) + } + return ptpb.MakeSchemaObjectsTarget(ids) + default: + t.Fatalf("malformed line %q, expecetd to find prefix %q or %q", target, tenantPrefix, + schemaObjectPrefix) + } + return nil +}