diff --git a/docs/generated/http/BUILD.bazel b/docs/generated/http/BUILD.bazel index 583fd386381a..ad6183f1a2a0 100644 --- a/docs/generated/http/BUILD.bazel +++ b/docs/generated/http/BUILD.bazel @@ -13,6 +13,7 @@ genrule( "//pkg/kv/kvserver/liveness/livenesspb:livenesspb_proto", "//pkg/kv/kvserver/loqrecovery/loqrecoverypb:loqrecoverypb_proto", "//pkg/kv/kvserver/readsummary/rspb:rspb_proto", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:tenantcapabilitiespb_proto", "//pkg/roachpb:roachpb_proto", "//pkg/server/diagnostics/diagnosticspb:diagnosticspb_proto", "//pkg/server/serverpb:serverpb_proto", diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel index 5b31bce23b3d..a7de6ac0b402 100644 --- a/pkg/BUILD.bazel +++ b/pkg/BUILD.bazel @@ -1261,6 +1261,7 @@ GO_TARGETS = [ "//pkg/kv:kv_test", "//pkg/multitenant/multitenantcpu:multitenantcpu", "//pkg/multitenant/multitenantio:multitenantio", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:tenantcapabilitiespb", "//pkg/multitenant/tenantcostmodel:tenantcostmodel", "//pkg/multitenant:multitenant", "//pkg/obs:obs", @@ -2592,6 +2593,7 @@ GET_X_DATA_TARGETS = [ "//pkg/multitenant:get_x_data", "//pkg/multitenant/multitenantcpu:get_x_data", "//pkg/multitenant/multitenantio:get_x_data", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:get_x_data", "//pkg/multitenant/tenantcostmodel:get_x_data", "//pkg/obs:get_x_data", "//pkg/obsservice/cmd/obsservice:get_x_data", diff --git a/pkg/ccl/backupccl/backup_test.go b/pkg/ccl/backupccl/backup_test.go index ea9477eef351..126deb9a3352 100644 --- a/pkg/ccl/backupccl/backup_test.go +++ b/pkg/ccl/backupccl/backup_test.go @@ -6891,14 +6891,28 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB := sqlutils.MakeSQLRunner(restoreTC.Conns[0]) restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + {`1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) restoreDB.CheckQueryResults(t, `SELECT id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `true`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }, ) restoreDB.CheckQueryResults(t, @@ -6927,8 +6941,18 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `false`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "DROP", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `false`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "DROP", "tenantReplicationJobId": "0"}`, + }, }, ) @@ -6952,8 +6976,18 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `true`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }, ) @@ -6977,14 +7011,29 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `true`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }, ) }) @@ -7006,14 +7055,29 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/clusterwide'`) restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `true`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }, ) @@ -7046,16 +7110,40 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, + `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }) restoreDB.Exec(t, `RESTORE FROM 'nodelocal://1/clusterwide'`) restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, `true`, `system`, `{"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`10`, `true`, `NULL`, `{"droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`11`, `true`, `NULL`, `{"droppedName": "", "id": "11", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, - {`20`, `true`, `NULL`, `{"droppedName": "", "id": "20", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`}, + { + `1`, + `true`, `system`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `10`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `11`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "11", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, + { + `20`, + `true`, + `NULL`, + `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "20", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + }, }, ) diff --git a/pkg/ccl/backupccl/testdata/backup-restore/alter-schedule/schedule-options b/pkg/ccl/backupccl/testdata/backup-restore/alter-schedule/schedule-options index c7f441be09ba..e310dea3fb40 100644 --- a/pkg/ccl/backupccl/testdata/backup-restore/alter-schedule/schedule-options +++ b/pkg/ccl/backupccl/testdata/backup-restore/alter-schedule/schedule-options @@ -78,4 +78,3 @@ exec-sql user=testuser expect-error-regex=(only users with the admin role are al alter backup schedule $fullID set schedule option updates_cluster_last_backup_time_metric = '1'; ---- regex matches error - diff --git a/pkg/ccl/backupccl/testdata/backup-restore/drop-schedule-backup b/pkg/ccl/backupccl/testdata/backup-restore/drop-schedule-backup index 12e904ef7b2e..a109d2f0aa17 100644 --- a/pkg/ccl/backupccl/testdata/backup-restore/drop-schedule-backup +++ b/pkg/ccl/backupccl/testdata/backup-restore/drop-schedule-backup @@ -28,4 +28,3 @@ query-sql SELECT * FROM [SHOW SCHEDULES] WHERE label='hello'; ---- - diff --git a/pkg/ccl/backupccl/testdata/backup-restore/in-progress-imports b/pkg/ccl/backupccl/testdata/backup-restore/in-progress-imports index 3bb53b973180..c8d012cd00c5 100644 --- a/pkg/ccl/backupccl/testdata/backup-restore/in-progress-imports +++ b/pkg/ccl/backupccl/testdata/backup-restore/in-progress-imports @@ -584,4 +584,3 @@ foofoo baz show_cluster_backup show_database_backup - diff --git a/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants b/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants index b64e0bdd5d11..ba008aad7637 100644 --- a/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants +++ b/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants @@ -21,9 +21,9 @@ SELECT crdb_internal.destroy_tenant(5); query-sql SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} exec-sql BACKUP INTO 'nodelocal://1/cluster' @@ -49,9 +49,9 @@ RESTORE FROM LATEST IN 'nodelocal://1/cluster' query-sql SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} exec-sql RESTORE TENANT 6 FROM LATEST IN 'nodelocal://1/tenant6' WITH tenant = '7'; @@ -60,7 +60,7 @@ RESTORE TENANT 6 FROM LATEST IN 'nodelocal://1/tenant6' WITH tenant = '7'; query-sql SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} -7 true {"droppedName": "", "id": "7", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} +7 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "7", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} diff --git a/pkg/gen/protobuf.bzl b/pkg/gen/protobuf.bzl index f0f1c539b554..ac2471328c57 100644 --- a/pkg/gen/protobuf.bzl +++ b/pkg/gen/protobuf.bzl @@ -37,6 +37,7 @@ PROTOBUF_SRCS = [ "//pkg/kv/kvserver/rangelog/internal/rangelogtestpb:rangelogtestpb_go_proto", "//pkg/kv/kvserver/readsummary/rspb:rspb_go_proto", "//pkg/kv/kvserver:kvserver_go_proto", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:tenantcapabilitiespb_go_proto", "//pkg/obsservice/obspb/opentelemetry-proto/common/v1:v1_go_proto", "//pkg/obsservice/obspb/opentelemetry-proto/logs/v1:v1_go_proto", "//pkg/obsservice/obspb/opentelemetry-proto/resource/v1:v1_go_proto", diff --git a/pkg/kv/kvserver/replica_init.go b/pkg/kv/kvserver/replica_init.go index d1c59716cd0a..28a4eea7d9d6 100644 --- a/pkg/kv/kvserver/replica_init.go +++ b/pkg/kv/kvserver/replica_init.go @@ -13,7 +13,6 @@ package kvserver import ( "bytes" "context" - "math/rand" "time" "github.com/cockroachdb/cockroach/pkg/keys" @@ -96,8 +95,7 @@ func newUnloadedReplica( r.mu.stateLoader = stateloader.Make(desc.RangeID) r.mu.quiescent = true r.mu.conf = store.cfg.DefaultSpanConfig - randSource := rand.New(rand.NewSource(timeutil.Now().UnixNano())) - split.Init(&r.loadBasedSplitter, store.cfg.Settings, randSource, func() float64 { + split.Init(&r.loadBasedSplitter, store.cfg.Settings, split.GlobalRandSource(), func() float64 { return float64(SplitByLoadQPSThreshold.Get(&store.cfg.Settings.SV)) }, func() time.Duration { return kvserverbase.SplitByLoadMergeDelay.Get(&store.cfg.Settings.SV) diff --git a/pkg/kv/kvserver/split/decider.go b/pkg/kv/kvserver/split/decider.go index 0c5ea9165210..e34937bb4641 100644 --- a/pkg/kv/kvserver/split/decider.go +++ b/pkg/kv/kvserver/split/decider.go @@ -15,6 +15,7 @@ package split import ( "context" "fmt" + "math/rand" "time" "github.com/cockroachdb/cockroach/pkg/keys" @@ -64,6 +65,27 @@ type RandSource interface { Intn(n int) int } +// globalRandSource implements the RandSource interface. +type globalRandSource struct{} + +// Float64 returns, as a float64, a pseudo-random number in the half-open +// interval [0.0,1.0) from the RandSource. +func (g globalRandSource) Float64() float64 { + return rand.Float64() +} + +// Intn returns, as an int, a non-negative pseudo-random number in the +// half-open interval [0,n). +func (g globalRandSource) Intn(n int) int { + return rand.Intn(n) +} + +// GlobalRandSource returns an implementation of the RandSource interface that +// redirects calls to the global rand. +func GlobalRandSource() RandSource { + return globalRandSource{} +} + var enableUnweightedLBSplitFinder = settings.RegisterBoolSetting( settings.SystemOnly, "kv.unweighted_lb_split_finder.enabled", diff --git a/pkg/multitenant/tenantcapabilities/BUILD.bazel b/pkg/multitenant/tenantcapabilities/BUILD.bazel new file mode 100644 index 000000000000..542033fbdcb9 --- /dev/null +++ b/pkg/multitenant/tenantcapabilities/BUILD.bazel @@ -0,0 +1 @@ +load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data") diff --git a/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/BUILD.bazel b/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/BUILD.bazel new file mode 100644 index 000000000000..17dc3498e8b2 --- /dev/null +++ b/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/BUILD.bazel @@ -0,0 +1,30 @@ +load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") + +proto_library( + name = "tenantcapabilitiespb_proto", + srcs = ["capabilities.proto"], + strip_import_prefix = "/pkg", + visibility = ["//visibility:public"], + deps = ["@com_github_gogo_protobuf//gogoproto:gogo_proto"], +) + +go_proto_library( + name = "tenantcapabilitiespb_go_proto", + compilers = ["//pkg/cmd/protoc-gen-gogoroach:protoc-gen-gogoroach_compiler"], + importpath = "github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb", + proto = ":tenantcapabilitiespb_proto", + visibility = ["//visibility:public"], + deps = ["@com_github_gogo_protobuf//gogoproto"], +) + +go_library( + name = "tenantcapabilitiespb", + embed = [":tenantcapabilitiespb_go_proto"], + importpath = "github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb", + visibility = ["//visibility:public"], +) + +get_x_data(name = "get_x_data") diff --git a/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/capabilities.proto b/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/capabilities.proto new file mode 100644 index 000000000000..8c3e27d19917 --- /dev/null +++ b/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb/capabilities.proto @@ -0,0 +1,32 @@ +// Copyright 2023 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +syntax = "proto3"; +package cockroach.multitenant.tenantcapabilities.tenantcapabilitiespb; +option go_package = "tenantcapabilitiespb"; + +import "gogoproto/gogo.proto"; + +// TenantCapabilities encapsulates a set of capabilities[1] for a specific +// tenant. Capabilities for a specific tenant are stored in the system.tenants +// table and are checked against in KV when the tenant performs a privileged +// operation. +// +// [1] Certain requests in the system are considered "privileged", and as such, +// tenants are only allowed to perform them if they have the appropriate +// capability. For example, performing an AdminSplit. +message TenantCapabilities { + option (gogoproto.equal) = true; + + // CanAdminSplit, if set to true, grants grants the tenant the ability to + // successfully perform `AdminSplit` requests. + bool can_admin_split = 1; +}; + diff --git a/pkg/sql/catalog/descpb/BUILD.bazel b/pkg/sql/catalog/descpb/BUILD.bazel index 0f63b3b1511e..4514ae11e43a 100644 --- a/pkg/sql/catalog/descpb/BUILD.bazel +++ b/pkg/sql/catalog/descpb/BUILD.bazel @@ -59,6 +59,7 @@ proto_library( deps = [ "//pkg/config/zonepb:zonepb_proto", "//pkg/geo/geoindex:geoindex_proto", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:tenantcapabilitiespb_proto", "//pkg/roachpb:roachpb_proto", "//pkg/sql/catalog/catenumpb:catenumpb_proto", "//pkg/sql/catalog/catpb:catpb_proto", @@ -78,6 +79,7 @@ go_proto_library( deps = [ "//pkg/config/zonepb", "//pkg/geo/geoindex", + "//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb", "//pkg/roachpb", # keep "//pkg/sql/catalog/catenumpb", "//pkg/sql/catalog/catpb", diff --git a/pkg/sql/catalog/descpb/tenant.proto b/pkg/sql/catalog/descpb/tenant.proto index 0cc971cdc51b..13e80c1c0a50 100644 --- a/pkg/sql/catalog/descpb/tenant.proto +++ b/pkg/sql/catalog/descpb/tenant.proto @@ -14,6 +14,7 @@ option go_package = "descpb"; import "gogoproto/gogo.proto"; import "roachpb/api.proto"; +import "multitenant/tenantcapabilities/tenantcapabilitiespb/capabilities.proto"; // TenantInfo represents a tenant in a multi-tenant cluster and is // stored in the "info" column of the "system.tenants" table. The @@ -51,6 +52,12 @@ message TenantInfo { (gogoproto.nullable) = false, (gogoproto.customname) = "TenantReplicationJobID", (gogoproto.casttype) = "github.com/cockroachdb/cockroach/pkg/sql/catalog/catpb.JobID"]; + + // Capabilities encapsulate a set of capabilities that a specific tenant + // possesses. + optional multitenant.tenantcapabilities.tenantcapabilitiespb.TenantCapabilities capabilities = 6 [ + (gogoproto.nullable) = false + ]; } // TenantInfoAndUsage contains the information for a tenant in a multi-tenant diff --git a/pkg/sql/gcjob_test/gc_job_test.go b/pkg/sql/gcjob_test/gc_job_test.go index ca12c26e7f3b..e52f14db5346 100644 --- a/pkg/sql/gcjob_test/gc_job_test.go +++ b/pkg/sql/gcjob_test/gc_job_test.go @@ -494,7 +494,7 @@ func TestGCTenant(t *testing.T) { require.EqualError( t, gcClosure(dropTenID, progress), - `GC state for tenant id:11 state:DROP name:"" dropped_name:"" tenant_replication_job_id:0 is DELETED yet the tenant row still exists`, + `GC state for tenant id:11 state:DROP name:"" dropped_name:"" tenant_replication_job_id:0 capabilities:<> is DELETED yet the tenant row still exists`, ) }) diff --git a/pkg/sql/logictest/testdata/logic_test/tenant b/pkg/sql/logictest/testdata/logic_test/tenant index 5600ad0618aa..cb8ebd0f4be4 100644 --- a/pkg/sql/logictest/testdata/logic_test/tenant +++ b/pkg/sql/logictest/testdata/logic_test/tenant @@ -3,7 +3,7 @@ query IBIT colnames SELECT id, active, length(info), name FROM system.tenants ORDER BY id ---- id active length name -1 true 16 system +1 true 18 system # Create a few tenants. @@ -38,10 +38,10 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-one {"droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} -3 true two {"droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} -4 true three {"droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} query ITT colnames SHOW TENANT system @@ -119,7 +119,7 @@ FROM system.tenants WHERE name = 'four' ORDER BY id ---- id active name crdb_internal.pb_to_json -5 true four {"droppedName": "", "id": "5", "name": "four", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 true four {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "four", "state": "ACTIVE", "tenantReplicationJobId": "0"} statement ok DROP TENANT four @@ -173,14 +173,14 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-one {"droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} -3 true two {"droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} -4 true three {"droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false NULL {"droppedName": "four", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 false NULL {"droppedName": "five-requiring-quotes", "id": "6", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -7 false NULL {"droppedName": "to-be-reclaimed", "id": "7", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -8 true to-be-reclaimed {"droppedName": "", "id": "8", "name": "to-be-reclaimed", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "four", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +6 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "five-requiring-quotes", "id": "6", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +7 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "to-be-reclaimed", "id": "7", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +8 true to-be-reclaimed {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "8", "name": "to-be-reclaimed", "state": "ACTIVE", "tenantReplicationJobId": "0"} # More valid tenant names. statement ok diff --git a/pkg/sql/logictest/testdata/logic_test/tenant_builtins b/pkg/sql/logictest/testdata/logic_test/tenant_builtins index 98d3cf3292b9..4d2899293eeb 100644 --- a/pkg/sql/logictest/testdata/logic_test/tenant_builtins +++ b/pkg/sql/logictest/testdata/logic_test/tenant_builtins @@ -3,7 +3,7 @@ query IBIT colnames SELECT id, active, length(info), name FROM system.tenants ORDER BY id ---- id active length name -1 true 16 system +1 true 18 system # Create three tenants. @@ -37,10 +37,10 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 true NULL {"droppedName": "", "id": "5", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 true NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "", "state": "ACTIVE", "tenantReplicationJobId": "0"} +10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} # Check we can add a name where none existed before. query I @@ -113,10 +113,10 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false NULL {"droppedName": "my-new-tenant-name", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "my-new-tenant-name", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} +10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} # Try to recreate an existing tenant. @@ -223,9 +223,9 @@ FROM system.tenants ORDER BY id ---- id active crdb_internal.pb_to_json -1 true {"droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true {"droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -10 true {"droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} +2 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} +10 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} query error tenant resource limits require a CCL binary SELECT crdb_internal.update_tenant_resource_limits(10, 1000, 100, 0, now(), 0)