From 989e615b8ad2f93dd0b60b009006a0c8b8084fd1 Mon Sep 17 00:00:00 2001 From: Tobias Grieger Date: Thu, 16 Mar 2023 10:10:30 +0100 Subject: [PATCH 1/3] kvserver: deflake TestReplicaProbeRequest When we ignored an ambigous result but the probe didn't actually happen, a later condition in the test would fail. Retry the probe on ambiguous results instead; the test already only expects the probe to happen "at least once", so we don't introduce any new issues should a successful probe end up being retried. Fixes https://github.com/cockroachdb/cockroach/issues/97136. Epic: none Release note: None --- pkg/kv/kvserver/replica_probe_test.go | 30 ++++++++++++++------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/pkg/kv/kvserver/replica_probe_test.go b/pkg/kv/kvserver/replica_probe_test.go index 47b389c156a2..b957fe68af02 100644 --- a/pkg/kv/kvserver/replica_probe_test.go +++ b/pkg/kv/kvserver/replica_probe_test.go @@ -125,20 +125,22 @@ func TestReplicaProbeRequest(t *testing.T) { kvpb.RoutingPolicy_LEASEHOLDER, kvpb.RoutingPolicy_NEAREST, } { - var b kv.Batch - b.AddRawRequest(probeReq) - b.Header.RoutingPolicy = policy - err := db.Run(ctx, &b) - if errors.HasType(err, (*kvpb.AmbiguousResultError)(nil)) { - // Rare but it can happen that we're proposing on a replica - // that is just about to get a snapshot. In that case we'll - // get: - // - // result is ambiguous: unable to determine whether command was applied via snapshot - t.Logf("ignoring: %s", err) - err = nil - } - require.NoError(t, err) + testutils.SucceedsSoon(t, func() error { + var b kv.Batch + b.AddRawRequest(probeReq) + b.Header.RoutingPolicy = policy + err := db.Run(ctx, &b) + if errors.HasType(err, (*kvpb.AmbiguousResultError)(nil)) { + // Rare but it can happen that we're proposing on a replica + // that is just about to get a snapshot. In that case we'll + // get: + // + // result is ambiguous: unable to determine whether command was applied via snapshot + return errors.Wrapf(err, "retrying") + } + require.NoError(t, err) + return nil + }) } } // Check expected number of probes seen on each Replica in the apply loop. From 6e4dd83b407e3fb70938b92038a4964de4410211 Mon Sep 17 00:00:00 2001 From: Pavel Kalinnikov Date: Wed, 15 Mar 2023 21:49:11 +0000 Subject: [PATCH 2/3] go.mod: bump etcd-io/raft to 5fe1c31 Fixes #97926 Epic: none Release note (bug fix): fixed a rare panic in upstream etcd-io/raft when message appends race with log compaction --- DEPS.bzl | 32 ++++++------------------------- build/bazelutil/distdir_files.bzl | 6 ++---- go.mod | 4 ++-- go.sum | 7 ++++--- 4 files changed, 14 insertions(+), 35 deletions(-) diff --git a/DEPS.bzl b/DEPS.bzl index 4c95bcc518fb..0e7641b251ec 100644 --- a/DEPS.bzl +++ b/DEPS.bzl @@ -1198,16 +1198,6 @@ def go_deps(): "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/census-instrumentation/opencensus-proto/com_github_census_instrumentation_opencensus_proto-v0.2.1.zip", ], ) - go_repository( - name = "com_github_certifi_gocertifi", - build_file_proto_mode = "disable_global", - importpath = "github.com/certifi/gocertifi", - sha256 = "11d525844c3dd711fb0ae31acc9ebd8a4d602215f14ff24ad1764ecb48464849", - strip_prefix = "github.com/certifi/gocertifi@v0.0.0-20200922220541-2c3bb06c6054", - urls = [ - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/certifi/gocertifi/com_github_certifi_gocertifi-v0.0.0-20200922220541-2c3bb06c6054.zip", - ], - ) go_repository( name = "com_github_cespare_xxhash", build_file_proto_mode = "disable_global", @@ -2812,16 +2802,6 @@ def go_deps(): "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/getkin/kin-openapi/com_github_getkin_kin_openapi-v0.53.0.zip", ], ) - go_repository( - name = "com_github_getsentry_raven_go", - build_file_proto_mode = "disable_global", - importpath = "github.com/getsentry/raven-go", - sha256 = "eaffe69939612cd05f95e1846b8ddb4043655571be34cdb6412a66b41b6826eb", - strip_prefix = "github.com/getsentry/raven-go@v0.2.0", - urls = [ - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/getsentry/raven-go/com_github_getsentry_raven_go-v0.2.0.zip", - ], - ) go_repository( name = "com_github_getsentry_sentry_go", build_file_proto_mode = "disable_global", @@ -8054,10 +8034,10 @@ def go_deps(): name = "com_github_stretchr_testify", build_file_proto_mode = "disable_global", importpath = "github.com/stretchr/testify", - sha256 = "36f64e4f229f87672ef8de1c756648c4165e76abd034362517578397e824856c", - strip_prefix = "github.com/stretchr/testify@v1.8.1", + sha256 = "400e18c88e5c4beb7ecca5d675048f1915a6e675b30fc03f8f563eb4dfde079a", + strip_prefix = "github.com/stretchr/testify@v1.8.2", urls = [ - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/stretchr/testify/com_github_stretchr_testify-v1.8.1.zip", + "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/stretchr/testify/com_github_stretchr_testify-v1.8.2.zip", ], ) go_repository( @@ -10341,10 +10321,10 @@ def go_deps(): ], build_file_proto_mode = "default", importpath = "go.etcd.io/raft/v3", - sha256 = "b27a4c7ddef64664745eaa5c817c80f3a7dc0d07d3b7837c02fde20993861ac7", - strip_prefix = "go.etcd.io/raft/v3@v3.0.0-20221221215055-65a0bf3b6779", + sha256 = "a20a4dc3311336d6cf64bf8a436a6bed8095748733af309435df8de3b794e36f", + strip_prefix = "go.etcd.io/raft/v3@v3.0.0-20230315220435-5fe1c31c5158", urls = [ - "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/raft/v3/io_etcd_go_raft_v3-v3.0.0-20221221215055-65a0bf3b6779.zip", + "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/raft/v3/io_etcd_go_raft_v3-v3.0.0-20230315220435-5fe1c31c5158.zip", ], ) go_repository( diff --git a/build/bazelutil/distdir_files.bzl b/build/bazelutil/distdir_files.bzl index 9a0a8bb15911..ddea3b1c744c 100644 --- a/build/bazelutil/distdir_files.bzl +++ b/build/bazelutil/distdir_files.bzl @@ -280,7 +280,6 @@ DISTDIR_FILES = { "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/cenkalti/backoff/com_github_cenkalti_backoff-v2.2.1+incompatible.zip": "f8196815a1b4d25e5b8158029d5264801fc8aa5ff128ccf30752fd169693d43b", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/cenkalti/backoff/v4/com_github_cenkalti_backoff_v4-v4.1.3.zip": "73ff572a901c0307aa1c16db43812da7ca2555aa403cfdd9d3a239ecbdad2274", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/census-instrumentation/opencensus-proto/com_github_census_instrumentation_opencensus_proto-v0.2.1.zip": "b3c09f3e635d47b4138695a547d1f2c7138f382cbe5a8b5865b66a8e08233461", - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/certifi/gocertifi/com_github_certifi_gocertifi-v0.0.0-20200922220541-2c3bb06c6054.zip": "11d525844c3dd711fb0ae31acc9ebd8a4d602215f14ff24ad1764ecb48464849", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/cespare/xxhash/com_github_cespare_xxhash-v1.1.0.zip": "fe98c56670b21631f7fd3305a29a3b17e86a6cce3876a2119460717a18538e2e", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/cespare/xxhash/v2/com_github_cespare_xxhash_v2-v2.2.0.zip": "fc180cdb0c00fbffbd39b774a72cdb5f0c32ace25370d5135195918a8c3fbd25", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/charmbracelet/bubbles/com_github_charmbracelet_bubbles-v0.15.1-0.20230123181021-a6a12c4a31eb.zip": "72954af77ec32995cfdf218fd31e9357a0fbef96f252bb1a9e6f0b8f158d3531", @@ -434,7 +433,6 @@ DISTDIR_FILES = { "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/garyburd/redigo/com_github_garyburd_redigo-v0.0.0-20150301180006-535138d7bcd7.zip": "7ed5f8194388955d2f086c170960cb096ee28d421b32bd12328d5f2a2b0ad488", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/gavv/httpexpect/com_github_gavv_httpexpect-v2.0.0+incompatible.zip": "3db05c59a5c70d11b9452727c529be6934ddf8b42f4bfdc3138441055f1529b1", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/getkin/kin-openapi/com_github_getkin_kin_openapi-v0.53.0.zip": "e3a00cb5828f8922087a0a74aad06c6177fa2eab44763a19aeec38f7fab7834b", - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/getsentry/raven-go/com_github_getsentry_raven_go-v0.2.0.zip": "eaffe69939612cd05f95e1846b8ddb4043655571be34cdb6412a66b41b6826eb", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/getsentry/sentry-go/com_github_getsentry_sentry_go-v0.12.0.zip": "1d127ea620f897c64de60982882cea34a4bb9c1481a9c9a379226af7cfa454ba", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/ghemawat/stream/com_github_ghemawat_stream-v0.0.0-20171120220530-696b145b53b9.zip": "9c0a42cacc8e22024b58db15127886a6f8ddbcfbf89d4d062bfdc43dc40d80d5", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/ghodss/yaml/com_github_ghodss_yaml-v1.0.0.zip": "c3f295d23c02c0b35e4d3b29053586e737cf9642df9615da99c0bda9bbacc624", @@ -928,7 +926,7 @@ DISTDIR_FILES = { "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/streadway/handy/com_github_streadway_handy-v0.0.0-20190108123426-d5acb3125c2a.zip": "f770ed96081220a9cbc5e975a06c2858b4f3d02820cb9902982116af491b171f", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/streadway/quantile/com_github_streadway_quantile-v0.0.0-20150917103942-b0c588724d25.zip": "45156bab62475784e2eacb349570c86bcf245a84d97825ce9ee2bf604a4438d5", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/stretchr/objx/com_github_stretchr_objx-v0.5.0.zip": "1a00b3bb5ad41cb72634ace06b7eb7df857404d77a7cab4e401a7c729561fe4c", - "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/stretchr/testify/com_github_stretchr_testify-v1.8.1.zip": "36f64e4f229f87672ef8de1c756648c4165e76abd034362517578397e824856c", + "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/stretchr/testify/com_github_stretchr_testify-v1.8.2.zip": "400e18c88e5c4beb7ecca5d675048f1915a6e675b30fc03f8f563eb4dfde079a", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/subosito/gotenv/com_github_subosito_gotenv-v1.2.0.zip": "21474df92536f36de6f91dfbf466995289445cc4e5a5900d9c40ae8776b8b0cf", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/syndtr/gocapability/com_github_syndtr_gocapability-v0.0.0-20200815063812-42c35b437635.zip": "91ff91da1936e17aa68fc13756e40ba4db1d7c9375a4ef0969fe19c9aa281195", "https://storage.googleapis.com/cockroach-godeps/gomod/github.com/tchap/go-patricia/com_github_tchap_go_patricia-v2.2.6+incompatible.zip": "948494017eae153a8c2d4ae9b450fd42abcb2578211f1c28e69ab71a2f27814d", @@ -998,7 +996,7 @@ DISTDIR_FILES = { "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/etcd/client/pkg/v3/io_etcd_go_etcd_client_pkg_v3-v3.5.0.zip": "c0ca209767c5734c6ed023888ba5be02aab5bd3c4d018999467f2bfa8bf65ee3", "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/etcd/client/v2/io_etcd_go_etcd_client_v2-v2.305.0.zip": "91fcb507fe8c193844b56bfb6c8741aaeb6ffa11ee9043de2af0f141173679f3", "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/etcd/io_etcd_go_etcd-v0.5.0-alpha.5.0.20200910180754-dd1b699fc489.zip": "d982ee501979b41b68625693bad77d15e4ae79ab9d0eae5f6028205f96a74e49", - "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/raft/v3/io_etcd_go_raft_v3-v3.0.0-20221221215055-65a0bf3b6779.zip": "b27a4c7ddef64664745eaa5c817c80f3a7dc0d07d3b7837c02fde20993861ac7", + "https://storage.googleapis.com/cockroach-godeps/gomod/go.etcd.io/raft/v3/io_etcd_go_raft_v3-v3.0.0-20230315220435-5fe1c31c5158.zip": "a20a4dc3311336d6cf64bf8a436a6bed8095748733af309435df8de3b794e36f", "https://storage.googleapis.com/cockroach-godeps/gomod/go.mongodb.org/mongo-driver/org_mongodb_go_mongo_driver-v1.5.1.zip": "446cff132e82c64af7ffcf48e268eb16ec81f694914aa6baecb06cbbae1be0d7", "https://storage.googleapis.com/cockroach-godeps/gomod/go.mozilla.org/pkcs7/org_mozilla_go_pkcs7-v0.0.0-20200128120323-432b2356ecb1.zip": "3c4c1667907ff3127e371d44696326bad9e965216d4257917ae28e8b82a9e08d", "https://storage.googleapis.com/cockroach-godeps/gomod/go.opencensus.io/io_opencensus_go-v0.24.0.zip": "203a767d7f8e7c1ebe5588220ad168d1e15b14ae70a636de7ca9a4a88a7e0d0c", diff --git a/go.mod b/go.mod index 4a24a97ef224..28de05fa062f 100644 --- a/go.mod +++ b/go.mod @@ -205,14 +205,14 @@ require ( github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.2.1 github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.1 + github.com/stretchr/testify v1.8.2 github.com/twpayne/go-geom v1.4.2 github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad github.com/xdg-go/pbkdf2 v1.0.0 github.com/xdg-go/scram v1.1.2 github.com/xdg-go/stringprep v1.0.4 github.com/zabawaba99/go-gitignore v0.0.0-20200117185801-39e6bddfb292 - go.etcd.io/raft/v3 v3.0.0-20221221215055-65a0bf3b6779 + go.etcd.io/raft/v3 v3.0.0-20230315220435-5fe1c31c5158 go.opentelemetry.io/otel v1.0.0-RC3 go.opentelemetry.io/otel/exporters/jaeger v1.0.0-RC3 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.0-RC3 diff --git a/go.sum b/go.sum index f9f68a1c0b6d..648eb4feaf1b 100644 --- a/go.sum +++ b/go.sum @@ -2146,8 +2146,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -2277,8 +2278,8 @@ go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3C go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/raft/v3 v3.0.0-20221221215055-65a0bf3b6779 h1:GS8kfvdZ03yoCzHjYjZ2FV0CaNqhcnaAPjn0IHf565E= -go.etcd.io/raft/v3 v3.0.0-20221221215055-65a0bf3b6779/go.mod h1:eMshmuwXLWZrjHXN8ZgYrOMQRSbHqi5M84DEZWhG+o4= +go.etcd.io/raft/v3 v3.0.0-20230315220435-5fe1c31c5158 h1:JdVbiz8TIDFV4pyXRQ+z7dcJueHcqB4NwRGm7tnr4wo= +go.etcd.io/raft/v3 v3.0.0-20230315220435-5fe1c31c5158/go.mod h1:xa/jfCF4K9FpWEAXstT+Nw4ToWdmhJ8oeC5eburtypA= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= From 52f4ff9a9fed0a68d1fb7627f574c94bdb3d3ab4 Mon Sep 17 00:00:00 2001 From: Raphael 'kena' Poss Date: Tue, 14 Mar 2023 14:54:27 +0100 Subject: [PATCH 3/3] sql: support tenant configuration templates This change introduces the LIKE clause to CREATE TENANT, which makes CREATE TENANT copy the parameters (but not the storage keyspace) from the tenant selected by LIKE. Also if LIKE is not specified, but the (new) cluster setting `sql.create_tenant.default_template` is not empty, the value of the cluster setting is used implicitly as LIKE clause. A proposed use of this is cluster-to-cluster replication, considering cutover as well. On the target (sink) cluster, the operator would do: ``` CREATE TENANT application LIKE app_template FROM REPLICATION OF application ON .... ``` And then cutover would look something like the following if they wanted the tenant to still be named "application" ``` ALTER TENANT application CUTOVER TO LATEST; DROP TENANT application; -- if there's one already ALTER TENANT application START SERVICE SHARED; ``` Release note: None --- .../streamingest/stream_ingestion_planning.go | 46 ++++-- pkg/multitenant/mtinfopb/info.proto | 19 ++- pkg/sql/create_tenant.go | 31 +++- pkg/sql/logictest/testdata/logic_test/tenant | 113 +++++++++++++ pkg/sql/parser/sql.y | 38 ++++- pkg/sql/parser/testdata/create_tenant | 32 ++++ pkg/sql/sem/tree/create.go | 18 +++ pkg/sql/sem/tree/walk.go | 39 +++++ pkg/sql/tenant_accessors.go | 149 ++++++++++++++++++ pkg/sql/tenant_creation.go | 47 ++++-- 10 files changed, 493 insertions(+), 39 deletions(-) diff --git a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go index 00327803b65f..07c11954fce3 100644 --- a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go +++ b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go @@ -48,6 +48,7 @@ func streamIngestionJobDescription( ReplicationSourceTenantName: streamIngestion.ReplicationSourceTenantName, ReplicationSourceAddress: tree.NewDString(redactedSourceAddr), Options: streamIngestion.Options, + Like: streamIngestion.Like, } ann := p.ExtendedEvalContext().Annotations return tree.AsStringWithFQNames(redactedCreateStmt, ann), nil @@ -64,12 +65,20 @@ func ingestionTypeCheck( if !ok { return false, nil, nil } - if err := exprutil.TypeCheck(ctx, "INGESTION", p.SemaCtx(), + toTypeCheck := []exprutil.ToTypeCheck{ exprutil.TenantSpec{TenantSpec: ingestionStmt.TenantSpec}, exprutil.TenantSpec{TenantSpec: ingestionStmt.ReplicationSourceTenantName}, exprutil.Strings{ ingestionStmt.ReplicationSourceAddress, - ingestionStmt.Options.Retention}); err != nil { + ingestionStmt.Options.Retention}, + } + if ingestionStmt.Like.OtherTenant != nil { + toTypeCheck = append(toTypeCheck, + exprutil.TenantSpec{TenantSpec: ingestionStmt.Like.OtherTenant}, + ) + } + + if err := exprutil.TypeCheck(ctx, "INGESTION", p.SemaCtx(), toTypeCheck...); err != nil { return false, nil, err } @@ -119,6 +128,15 @@ func ingestionPlanHook( return nil, nil, nil, false, err } + var likeTenantID uint64 + var likeTenantName string + if ingestionStmt.Like.OtherTenant != nil { + _, likeTenantID, likeTenantName, err = exprEval.TenantSpec(ctx, ingestionStmt.Like.OtherTenant) + if err != nil { + return nil, nil, nil, false, err + } + } + options, err := evalTenantReplicationOptions(ctx, ingestionStmt.Options, exprEval) if err != nil { return nil, nil, nil, false, err @@ -164,20 +182,20 @@ func ingestionPlanHook( sourceTenant, dstTenantName, dstTenantID) } + // Determine which template will be used as config template to + // create the new tenant below. + tenantInfo, err := sql.GetTenantTemplate(ctx, p.ExecCfg().Settings, p.InternalSQLTxn(), nil, likeTenantID, likeTenantName) + if err != nil { + return err + } + // Create a new tenant for the replication stream. jobID := p.ExecCfg().JobRegistry.MakeJobID() - tenantInfo := &mtinfopb.TenantInfoWithUsage{ - ProtoInfo: mtinfopb.ProtoInfo{ - TenantReplicationJobID: jobID, - }, - SQLInfo: mtinfopb.SQLInfo{ - // dstTenantID may be zero which will cause auto-allocation. - ID: dstTenantID, - DataState: mtinfopb.DataStateAdd, - ServiceMode: mtinfopb.ServiceModeNone, - Name: roachpb.TenantName(dstTenantName), - }, - } + tenantInfo.TenantReplicationJobID = jobID + // dstTenantID may be zero which will cause auto-allocation. + tenantInfo.ID = dstTenantID + tenantInfo.DataState = mtinfopb.DataStateAdd + tenantInfo.Name = roachpb.TenantName(dstTenantName) initialTenantZoneConfig, err := sql.GetHydratedZoneConfigForTenantsRange(ctx, p.Txn(), p.ExtendedEvalContext().Descs) if err != nil { diff --git a/pkg/multitenant/mtinfopb/info.proto b/pkg/multitenant/mtinfopb/info.proto index 2da7024b0f8f..f12c651d2990 100644 --- a/pkg/multitenant/mtinfopb/info.proto +++ b/pkg/multitenant/mtinfopb/info.proto @@ -103,10 +103,23 @@ message UsageInfo { // All-time consumption for this tenant. Each field has a corresponding column // in system.tenant_usage. optional roachpb.TenantConsumption consumption = 4 [(gogoproto.nullable) = false]; + + // Next ID: 5 +} + +// SettingOverride represents a cluster setting override for one tenant. +message SettingOverride { + option (gogoproto.equal) = true; + + optional string name = 1 [(gogoproto.nullable) = false]; + optional string value = 2 [(gogoproto.nullable) = false]; + optional string value_type = 3 [(gogoproto.nullable) = false]; + optional string reason = 4; + // Next ID: 5 } // TenantInfoWithUsage contains the information for a tenant in a multi-tenant -// cluster plus metadata related to cost control and consumption. +// cluster plus metadata related to cost control and consumption and setting overrides. message TenantInfoWithUsage { option (gogoproto.equal) = true; @@ -115,5 +128,7 @@ message TenantInfoWithUsage { optional SQLInfo extra_columns = 3 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; - // Next ID: 4 + repeated SettingOverride setting_overrides = 4; + + // Next ID: 5 } diff --git a/pkg/sql/create_tenant.go b/pkg/sql/create_tenant.go index 87446dfc8971..1109f9a3c0de 100644 --- a/pkg/sql/create_tenant.go +++ b/pkg/sql/create_tenant.go @@ -13,11 +13,14 @@ package sql import ( "context" + "github.com/cockroachdb/cockroach/pkg/multitenant/mtinfopb" "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/errors" ) type createTenantNode struct { - tenantSpec tenantSpec + tenantSpec tenantSpec + likeTenantSpec tenantSpec } func (p *planner) CreateTenantNode(ctx context.Context, n *tree.CreateTenant) (planNode, error) { @@ -25,8 +28,16 @@ func (p *planner) CreateTenantNode(ctx context.Context, n *tree.CreateTenant) (p if err != nil { return nil, err } + var likeTenantSpec tenantSpec + if n.Like.OtherTenant != nil { + likeTenantSpec, err = p.planTenantSpec(ctx, n.Like.OtherTenant, "CREATE TENANT LIKE") + if err != nil { + return nil, err + } + } return &createTenantNode{ - tenantSpec: tspec, + tenantSpec: tspec, + likeTenantSpec: likeTenantSpec, }, nil } @@ -36,6 +47,20 @@ func (n *createTenantNode) startExec(params runParams) error { return err } + var tmplInfo *mtinfopb.TenantInfo + if n.likeTenantSpec != nil { + tmplInfo, err = n.likeTenantSpec.getTenantInfo(params.ctx, params.p) + if err != nil { + return errors.Wrap(err, "retrieving record for LIKE configuration template") + } + } + configTemplate, err := GetTenantTemplate(params.ctx, + params.p.ExecCfg().Settings, params.p.InternalSQLTxn(), + tmplInfo, 0, "") + if err != nil { + return err + } + var ctcfg createTenantConfig if tenantName != "" { ctcfg.Name = (*string)(&tenantName) @@ -44,7 +69,7 @@ func (n *createTenantNode) startExec(params runParams) error { tenantID := tid.ToUint64() ctcfg.ID = &tenantID } - _, err = params.p.createTenantInternal(params.ctx, ctcfg) + _, err = params.p.createTenantInternal(params.ctx, ctcfg, configTemplate) return err } diff --git a/pkg/sql/logictest/testdata/logic_test/tenant b/pkg/sql/logictest/testdata/logic_test/tenant index 88fea1d94cc8..10d369875563 100644 --- a/pkg/sql/logictest/testdata/logic_test/tenant +++ b/pkg/sql/logictest/testdata/logic_test/tenant @@ -329,3 +329,116 @@ DROP TENANT two statement ok DROP TENANT 'tenant-one' + +subtest tenant_templates + +query T +SHOW CLUSTER SETTING sql.create_tenant.default_template +---- +ยท + +# Check we can't use the system tenant as template. +statement error using the system tenant as config template +CREATE TENANT othertenant LIKE system + +# Create some "interesting" tenant template. +statement ok +CREATE TENANT tmpl; +ALTER TENANT tmpl GRANT CAPABILITY can_view_node_info; -- will be copied +ALTER TENANT tmpl SET CLUSTER SETTING trace.debug.enable = true; -- will be copied +-- Simulate resource limits. Will be copied. +-- Note: we cannot use the update_tenant_resource_limits() builtin +-- directly here because it can only be used from a CCL binary. +INSERT INTO system.tenant_usage( + tenant_id, instance_id, next_instance_id, last_update, + ru_burst_limit, ru_refill_rate, ru_current, current_share_sum, total_consumption) +VALUES ((SELECT id FROM system.tenants WHERE name = 'tmpl'), 0, 0, now(), + 11, 22, 33, 44, ''::BYTES); +ALTER TENANT tmpl START SERVICE SHARED; -- will not be copied. + +# Use it to create a new tenant. +statement ok +CREATE TENANT othertenant LIKE tmpl + +# Verify the service mode was not copied. +query ITTT +SHOW TENANT othertenant +---- +13 othertenant ready none + +# Verify the new tenant has the same caps as the template +# (by showing there's no difference between the two) +query TT +SELECT capability_name, capability_value FROM [SHOW TENANT tmpl WITH CAPABILITIES] +EXCEPT SELECT capability_name, capability_value FROM [SHOW TENANT othertenant WITH CAPABILITIES]; +---- + +# Check that the setting overrides were copied. +query TTTT +SELECT variable, value, type, origin FROM [SHOW CLUSTER SETTINGS FOR TENANT othertenant] +WHERE origin != 'no-override' +---- +trace.debug.enable true b per-tenant-override + +# Check that the resource usage parameters were copied. +query IIRRRRI +SELECT instance_id, next_instance_id, + ru_burst_limit, ru_refill_rate, ru_current, + current_share_sum, length(total_consumption) +FROM system.tenant_usage WHERE tenant_id = (SELECT id FROM system.tenants WHERE name = 'othertenant') +---- +0 0 11 22 33 0 0 + +# Clean up. +statement ok +DROP TENANT othertenant + +# Now set the default template and try again. +statement ok +SET CLUSTER SETTING sql.create_tenant.default_template = 'nonexistent'; + +statement error retrieving default tenant configuration template.*tenant "nonexistent" does not exist +CREATE TENANT othertenant + +statement ok +SET CLUSTER SETTING sql.create_tenant.default_template = 'tmpl'; + +# Create a new tenant - this should use the template implicitly now. +statement ok +CREATE TENANT othertenant + +# Verify the service mode was not copied. +query ITTT +SHOW TENANT othertenant +---- +14 othertenant ready none + +query TT +SELECT capability_name, capability_value FROM [SHOW TENANT tmpl WITH CAPABILITIES] +EXCEPT SELECT capability_name, capability_value FROM [SHOW TENANT othertenant WITH CAPABILITIES]; +---- + +# Check the setting overrides were taken over. +query TTTT +SELECT variable, value, type, origin FROM [SHOW CLUSTER SETTINGS FOR TENANT othertenant] +WHERE origin != 'no-override' +---- +trace.debug.enable true b per-tenant-override + +# Check that the resource usage parameters were copied. +query IIRRRRI +SELECT instance_id, next_instance_id, + ru_burst_limit, ru_refill_rate, ru_current, + current_share_sum, length(total_consumption) +FROM system.tenant_usage WHERE tenant_id = (SELECT id FROM system.tenants WHERE name = 'othertenant') +---- +0 0 11 22 33 0 0 + +# Clean up. +statement ok +DROP TENANT othertenant; +ALTER TENANT tmpl STOP SERVICE; +DROP TENANT tmpl + +statement ok +RESET CLUSTER SETTING sql.create_tenant.default_template diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 676a5c07595e..9038ada803b3 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -855,6 +855,9 @@ func (u *sqlSymUnion) showRangesOpts() *tree.ShowRangesOptions { func (u *sqlSymUnion) tenantSpec() *tree.TenantSpec { return u.val.(*tree.TenantSpec) } +func (u *sqlSymUnion) likeTenantSpec() *tree.LikeTenantSpec { + return u.val.(*tree.LikeTenantSpec) +} func (u *sqlSymUnion) cteMaterializeClause() tree.CTEMaterializeClause { return u.val.(tree.CTEMaterializeClause) } @@ -1169,6 +1172,8 @@ func (u *sqlSymUnion) showTenantOpts() tree.ShowTenantOptions { %type create_sequence_stmt %type create_func_stmt +%type <*tree.LikeTenantSpec> opt_like_tenant + %type create_stats_stmt %type <*tree.CreateStatsOptions> opt_create_stats_options %type <*tree.CreateStatsOptions> create_stats_option_list @@ -4348,26 +4353,43 @@ create_stmt: // %Help: CREATE TENANT - create new tenant // %Category: Experimental // %Text: -// CREATE TENANT name -// CREATE TENANT name FROM REPLICATION OF ON [ WITH OPTIONS ... ] +// CREATE TENANT name [ LIKE ] +// CREATE TENANT name [ LIKE ] FROM REPLICATION OF ON [ WITH OPTIONS ... ] create_tenant_stmt: - CREATE TENANT d_expr + CREATE TENANT d_expr opt_like_tenant { /* SKIP DOC */ - $$.val = &tree.CreateTenant{TenantSpec: &tree.TenantSpec{IsName: true, Expr: $3.expr()}} + $$.val = &tree.CreateTenant{ + TenantSpec: &tree.TenantSpec{IsName: true, Expr: $3.expr()}, + Like: $4.likeTenantSpec(), + } } -| CREATE TENANT d_expr FROM REPLICATION OF d_expr ON d_expr opt_with_tenant_replication_options +| CREATE TENANT d_expr opt_like_tenant FROM REPLICATION OF d_expr ON d_expr opt_with_tenant_replication_options { /* SKIP DOC */ $$.val = &tree.CreateTenantFromReplication{ TenantSpec: &tree.TenantSpec{IsName: true, Expr: $3.expr()}, - ReplicationSourceTenantName: &tree.TenantSpec{IsName: true, Expr: $7.expr()}, - ReplicationSourceAddress: $9.expr(), - Options: *$10.tenantReplicationOptions(), + ReplicationSourceTenantName: &tree.TenantSpec{IsName: true, Expr: $8.expr()}, + ReplicationSourceAddress: $10.expr(), + Options: *$11.tenantReplicationOptions(), + Like: $4.likeTenantSpec(), } } | CREATE TENANT error // SHOW HELP: CREATE TENANT +// opt_like_tenant defines a LIKE clause for CREATE TENANT. +// Eventually this can grow to support INCLUDING/EXCLUDING options +// like in CREATE TABLE. +opt_like_tenant: + /* EMPTY */ + { + $$.val = &tree.LikeTenantSpec{} + } +| LIKE tenant_spec + { + $$.val = &tree.LikeTenantSpec{OtherTenant: $2.tenantSpec()} + } + // Optional tenant replication options. opt_with_tenant_replication_options: WITH tenant_replication_options_list diff --git a/pkg/sql/parser/testdata/create_tenant b/pkg/sql/parser/testdata/create_tenant index 65a35dcf1cea..9e0077c78427 100644 --- a/pkg/sql/parser/testdata/create_tenant +++ b/pkg/sql/parser/testdata/create_tenant @@ -14,6 +14,22 @@ CREATE TENANT ("bar-with-hyphen") -- fully parenthesized CREATE TENANT "bar-with-hyphen" -- literals removed CREATE TENANT _ -- identifiers removed +parse +CREATE TENANT foo LIKE bar +---- +CREATE TENANT foo LIKE bar +CREATE TENANT (foo) LIKE (bar) -- fully parenthesized +CREATE TENANT foo LIKE bar -- literals removed +CREATE TENANT _ LIKE _ -- identifiers removed + +parse +CREATE TENANT foo LIKE [123] +---- +CREATE TENANT foo LIKE [123] +CREATE TENANT (foo) LIKE [(123)] -- fully parenthesized +CREATE TENANT foo LIKE [_] -- literals removed +CREATE TENANT _ LIKE [123] -- identifiers removed + parse CREATE TENANT destination FROM REPLICATION OF source ON 'pgurl' ---- @@ -22,6 +38,22 @@ CREATE TENANT (destination) FROM REPLICATION OF (source) ON ('pgurl') -- fully p CREATE TENANT destination FROM REPLICATION OF source ON '_' -- literals removed CREATE TENANT _ FROM REPLICATION OF _ ON 'pgurl' -- identifiers removed +parse +CREATE TENANT destination LIKE bar FROM REPLICATION OF source ON 'pgurl' +---- +CREATE TENANT destination LIKE bar FROM REPLICATION OF source ON 'pgurl' +CREATE TENANT (destination) LIKE (bar) FROM REPLICATION OF (source) ON ('pgurl') -- fully parenthesized +CREATE TENANT destination LIKE bar FROM REPLICATION OF source ON '_' -- literals removed +CREATE TENANT _ LIKE _ FROM REPLICATION OF _ ON 'pgurl' -- identifiers removed + +parse +CREATE TENANT destination LIKE [123] FROM REPLICATION OF source ON 'pgurl' +---- +CREATE TENANT destination LIKE [123] FROM REPLICATION OF source ON 'pgurl' +CREATE TENANT (destination) LIKE [(123)] FROM REPLICATION OF (source) ON ('pgurl') -- fully parenthesized +CREATE TENANT destination LIKE [_] FROM REPLICATION OF source ON '_' -- literals removed +CREATE TENANT _ LIKE [123] FROM REPLICATION OF _ ON 'pgurl' -- identifiers removed + parse CREATE TENANT "destination-hyphen" FROM REPLICATION OF "source-hyphen" ON 'pgurl' ---- diff --git a/pkg/sql/sem/tree/create.go b/pkg/sql/sem/tree/create.go index 1512a3322944..943aa89aa567 100644 --- a/pkg/sql/sem/tree/create.go +++ b/pkg/sql/sem/tree/create.go @@ -2150,12 +2150,27 @@ func (node *CreateExternalConnection) Format(ctx *FmtCtx) { // CreateTenant represents a CREATE TENANT statement. type CreateTenant struct { TenantSpec *TenantSpec + Like *LikeTenantSpec } // Format implements the NodeFormatter interface. func (node *CreateTenant) Format(ctx *FmtCtx) { ctx.WriteString("CREATE TENANT ") ctx.FormatNode(node.TenantSpec) + ctx.FormatNode(node.Like) +} + +// LikeTenantSpec represents a LIKE clause in CREATE TENANT. +type LikeTenantSpec struct { + OtherTenant *TenantSpec +} + +func (node *LikeTenantSpec) Format(ctx *FmtCtx) { + if node.OtherTenant == nil { + return + } + ctx.WriteString(" LIKE ") + ctx.FormatNode(node.OtherTenant) } // CreateTenantFromReplication represents a CREATE TENANT...FROM REPLICATION @@ -2175,6 +2190,8 @@ type CreateTenantFromReplication struct { ReplicationSourceAddress Expr Options TenantReplicationOptions + + Like *LikeTenantSpec } // TenantReplicationOptions options for the CREATE TENANT FROM REPLICATION command. @@ -2190,6 +2207,7 @@ func (node *CreateTenantFromReplication) Format(ctx *FmtCtx) { // NB: we do not anonymize the tenant name because we assume that tenant names // do not contain sensitive information. ctx.FormatNode(node.TenantSpec) + ctx.FormatNode(node.Like) if node.ReplicationSourceAddress != nil { ctx.WriteString(" FROM REPLICATION OF ") diff --git a/pkg/sql/sem/tree/walk.go b/pkg/sql/sem/tree/walk.go index f16308122480..b6dc103523fb 100644 --- a/pkg/sql/sem/tree/walk.go +++ b/pkg/sql/sem/tree/walk.go @@ -1041,6 +1041,34 @@ func (n *AlterTenantReplication) walkStmt(v Visitor) Statement { return ret } +// copyNode makes a copy of this node without recursing. +func (n *LikeTenantSpec) copyNode() *LikeTenantSpec { + nodeCopy := *n + return &nodeCopy +} + +// copyNode makes a copy of this Statement without recursing in any child Statements. +func (n *CreateTenant) copyNode() *CreateTenant { + stmtCopy := *n + return &stmtCopy +} + +// walkStmt is part of the walkableStmt interface. +func (n *CreateTenant) walkStmt(v Visitor) Statement { + ret := n + if n.Like.OtherTenant != nil { + ts, changed := walkTenantSpec(v, n.TenantSpec) + if changed { + if ret == n { + ret = n.copyNode() + } + ret.Like = n.Like.copyNode() + ret.Like.OtherTenant = ts + } + } + return ret +} + // copyNode makes a copy of this Statement without recursing in any child Statements. func (n *CreateTenantFromReplication) copyNode() *CreateTenantFromReplication { stmtCopy := *n @@ -1073,6 +1101,16 @@ func (n *CreateTenantFromReplication) walkStmt(v Visitor) Statement { ret.Options.Retention = e } } + if n.Like.OtherTenant != nil { + ts, changed := walkTenantSpec(v, n.TenantSpec) + if changed { + if ret == n { + ret = n.copyNode() + } + ret.Like = n.Like.copyNode() + ret.Like.OtherTenant = ts + } + } return ret } @@ -1900,6 +1938,7 @@ var _ walkableStmt = &CancelSessions{} var _ walkableStmt = &ControlJobs{} var _ walkableStmt = &ControlSchedules{} var _ walkableStmt = &CreateTable{} +var _ walkableStmt = &CreateTenant{} var _ walkableStmt = &CreateTenantFromReplication{} var _ walkableStmt = &Delete{} var _ walkableStmt = &DropTenant{} diff --git a/pkg/sql/tenant_accessors.go b/pkg/sql/tenant_accessors.go index ce557c0cd6b7..c68538c98fe9 100644 --- a/pkg/sql/tenant_accessors.go +++ b/pkg/sql/tenant_accessors.go @@ -15,8 +15,10 @@ import ( "github.com/cockroachdb/cockroach/pkg/clusterversion" "github.com/cockroachdb/cockroach/pkg/keys" + "github.com/cockroachdb/cockroach/pkg/kv/kvpb" "github.com/cockroachdb/cockroach/pkg/multitenant/mtinfopb" "github.com/cockroachdb/cockroach/pkg/roachpb" + "github.com/cockroachdb/cockroach/pkg/settings" "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/cockroachdb/cockroach/pkg/sql/isql" "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" @@ -187,3 +189,150 @@ func (p *planner) LookupTenantID( } return roachpb.MustMakeTenantID(rec.ID), nil } + +// GetExtendedTenantInfo hydrates a TenantInfoWithUsage with the +// additional data beyond the TenantInfo record. +func GetExtendedTenantInfo( + ctx context.Context, txn isql.Txn, info *mtinfopb.TenantInfo, +) (*mtinfopb.TenantInfoWithUsage, error) { + res := &mtinfopb.TenantInfoWithUsage{ + ProtoInfo: info.ProtoInfo, + SQLInfo: info.SQLInfo, + } + rows, err := txn.QueryBufferedEx(ctx, "get-tenant-setting-overrides", txn.KV(), sessiondata.NodeUserSessionDataOverride, + `SELECT name, value, value_type, reason FROM system.tenant_settings WHERE tenant_id = $1`, + info.ID, + ) + if err != nil { + return nil, err + } + for _, row := range rows { + override := &mtinfopb.SettingOverride{ + Name: string(tree.MustBeDString(row[0])), + Value: string(tree.MustBeDString(row[1])), + ValueType: string(tree.MustBeDString(row[2])), + } + if row[3] != tree.DNull { + s := string(tree.MustBeDString(row[3])) + override.Reason = &s + } + res.SettingOverrides = append(res.SettingOverrides, override) + } + + row, err := txn.QueryRowEx(ctx, "get-tenant-usage-config", txn.KV(), sessiondata.NodeUserSessionDataOverride, + `SELECT ru_current, ru_burst_limit, ru_refill_rate + FROM system.tenant_usage + WHERE tenant_id = $1 AND instance_id = 0`, + info.ID, + ) + if err != nil { + return nil, err + } + if row != nil { + res.Usage = &mtinfopb.UsageInfo{ + RUCurrent: float64(tree.MustBeDFloat(row[0])), + RUBurstLimit: float64(tree.MustBeDFloat(row[1])), + RURefillRate: float64(tree.MustBeDFloat(row[2])), + } + } + + return res, nil +} + +var defaultTenantConfigTemplate = func() *settings.StringSetting { + s := settings.RegisterStringSetting( + settings.SystemOnly, + "sql.create_tenant.default_template", + "tenant to use as configuration template when LIKE is not specified in CREATE TENANT", + // We use the empty string so that no template is used by default + // (i.e. empty proto, no setting overrides). + "", + ) + s.SetReportable(true) + return s +}() + +// GetTenantTemplate loads the tenant template corresponding to the +// provided origin tenant. If info is nil, likeTenantID is zero and +// likeTenantName is empty, the default template is returned. +func GetTenantTemplate( + ctx context.Context, + settings *cluster.Settings, + txn isql.Txn, + info *mtinfopb.TenantInfo, + likeTenantID uint64, + likeTenantName string, +) (res *mtinfopb.TenantInfoWithUsage, err error) { + if info != nil && (likeTenantID != 0 || likeTenantName != "") { + // Sanity check + return nil, errors.AssertionFailedf("programming error: cannot pass both default info struct and tenant reference") + } + if info == nil { + if likeTenantID == 0 && likeTenantName == "" { + // No LIKE at all. Do we have something in the cluster setting? + tmplName := defaultTenantConfigTemplate.Get(&settings.SV) + if tmplName == "" { + // No template at all - just use an empty protobuf. + return &mtinfopb.TenantInfoWithUsage{}, nil + } + // Use the template specified in the setting. + info, err = GetTenantRecordByName(ctx, settings, txn, roachpb.TenantName(tmplName)) + if err != nil { + return nil, errors.Wrapf(err, "retrieving default tenant configuration template %q", tmplName) + } + } else { + if likeTenantID != 0 && likeTenantName != "" { + return nil, errors.AssertionFailedf("programming error: conflicting input tenant spec from caller") + } + // No pre-loaded info, but we have a LIKE clause. Is it by-ID or by-Name? + if likeTenantID != 0 { + // By-ID. + tid, err := roachpb.MakeTenantID(likeTenantID) + if err != nil { + return nil, errors.Wrap(err, "invalid LIKE tenant ID") + } + info, err = GetTenantRecordByID(ctx, txn, tid, settings) + if err != nil { + return nil, errors.Wrap(err, "retrieving LIKE tenant record") + } + } else { + // By-name. + info, err = GetTenantRecordByName(ctx, settings, txn, roachpb.TenantName(likeTenantName)) + if err != nil { + return nil, errors.Wrap(err, "retrieving LIKE tenant record") + } + } + } + } + + // For now, prevent use of the record for the system tenant. The + // user may have the mistaken assumption that "LIKE system" would + // create a tenant with all the special cases of the system tenant, + // and we do not guarantee that for now. + if roachpb.MustMakeTenantID(info.ID).IsSystem() { + return nil, errors.WithHint( + pgerror.New(pgcode.WrongObjectType, "using the system tenant as config template"), + "Create another secondary tenant as template, grant it extra capabilities, and then use that as config template.") + } + + // Now we have our info field. Expand it. + tmplInfo, err := GetExtendedTenantInfo(ctx, txn, info) + if err != nil { + return nil, errors.Wrap(err, "retrieving tenant template details") + } + + // Clear out the fields we can't reuse in a fresh tenant record. + tmplInfo.ID = 0 + tmplInfo.Name = "" + tmplInfo.DataState = mtinfopb.DataStateReady + tmplInfo.ServiceMode = mtinfopb.ServiceModeNone + tmplInfo.DroppedName = "" + tmplInfo.DeprecatedID = 0 + tmplInfo.DeprecatedDataState = 0 + tmplInfo.TenantReplicationJobID = 0 + if tmplInfo.Usage != nil { + tmplInfo.Usage.Consumption = kvpb.TenantConsumption{} + } + + return tmplInfo, nil +} diff --git a/pkg/sql/tenant_creation.go b/pkg/sql/tenant_creation.go index 3195226200e2..ca852c6fa845 100644 --- a/pkg/sql/tenant_creation.go +++ b/pkg/sql/tenant_creation.go @@ -59,7 +59,10 @@ func (p *planner) CreateTenant( return tid, pgerror.WithCandidateCode(err, pgcode.Syntax) } } - return p.createTenantInternal(ctx, ctcfg) + + configTemplate := mtinfopb.TenantInfoWithUsage{} + + return p.createTenantInternal(ctx, ctcfg, &configTemplate) } type createTenantConfig struct { @@ -69,7 +72,7 @@ type createTenantConfig struct { } func (p *planner) createTenantInternal( - ctx context.Context, ctcfg createTenantConfig, + ctx context.Context, ctcfg createTenantConfig, configTemplate *mtinfopb.TenantInfoWithUsage, ) (tid roachpb.TenantID, err error) { var tenantID uint64 if ctcfg.ID != nil { @@ -100,16 +103,17 @@ func (p *planner) createTenantInternal( return tid, err } - info := &mtinfopb.TenantInfoWithUsage{ - SQLInfo: mtinfopb.SQLInfo{ - ID: tenantID, - // We synchronously initialize the tenant's keyspace below, so - // we can skip the ADD state and go straight to the READY state. - DataState: mtinfopb.DataStateReady, - Name: name, - ServiceMode: serviceMode, - }, - } + info := configTemplate + + // Override the template fields for a fresh tenant. The other + // template fields remain unchanged (i.e. we reuse the template's + // configuration). + info.ID = tenantID + info.Name = name + // We synchronously initialize the tenant's keyspace below, so + // we can skip the ADD state and go straight to the READY state. + info.DataState = mtinfopb.DataStateReady + info.ServiceMode = serviceMode initialTenantZoneConfig, err := GetHydratedZoneConfigForTenantsRange(ctx, p.Txn(), p.Descriptors()) if err != nil { @@ -332,6 +336,25 @@ func CreateTenantRecord( logcrash.ReportOrPanic(ctx, &settings.SV, "inserting tenant %+v: unexpected number of rows affected: %d", info, num) } + for _, so := range info.SettingOverrides { + var reason interface{} + if so.Reason != nil { + reason = *so.Reason + } + if _, err := txn.ExecEx( + ctx, "create-tenant-setting-override", txn.KV(), sessiondata.NodeUserSessionDataOverride, + `INSERT INTO system.tenant_settings ( + tenant_id, name, value, value_type, reason) + VALUES ($1, $2, $3, $4, $5)`, + tenID, so.Name, so.Value, so.ValueType, reason); err != nil { + if pgerror.GetPGCode(err) == pgcode.UniqueViolation { + return roachpb.TenantID{}, pgerror.Newf(pgcode.DuplicateObject, "tenant \"%d\" already has an override for setting %q", + tenID, so.Name) + } + return roachpb.TenantID{}, errors.Wrap(err, "inserting tenant setting overrides") + } + } + if u := info.Usage; u != nil { consumption, err := protoutil.Marshal(&u.Consumption) if err != nil {