From f7ff204e12b631b798c84c7d77cafdac7f40ede7 Mon Sep 17 00:00:00 2001 From: Raphael 'kena' Poss Date: Fri, 20 Jan 2023 16:11:10 +0100 Subject: [PATCH] sql,ccl: new tenant info field ServiceMode Summary of changes: - the new TenantInfo.ServiceMode field indicates how to run servers. - new syntax: `ALTER TENANT ... START SERVICE EXTERNAL/SHARED`, `ALTER TENANT ... STOP SERVICE`. - tenants created via `create_tenant()` (via CC serverless control plane) start in service mode EXTERNAL. - other tenants start in service mode NONE. - need ALTER TENANT STOP SERVICE before dropping a tenant. - except in the case of `crdb_internal.destroy_tenant` for compat with CC serverless control plane. This commit does not contain the logic to start/stop servers yet. Release note: None --- docs/generated/sql/bnf/stmt_block.bnf | 3 + pkg/ccl/backupccl/backup_test.go | 36 +++---- pkg/ccl/backupccl/restore_job.go | 13 +-- .../testdata/backup-restore/restore-tenants | 22 ++-- .../testdata/logic_test/tenant_usage | 1 + .../tenant/directory_cache_test.go | 21 ++-- .../replication_stream_e2e_test.go | 4 +- .../streamingest/stream_ingestion_planning.go | 3 +- .../streamingccl/streamingest/testdata/simple | 8 +- pkg/ccl/testccl/sqlccl/tenant_gc_test.go | 4 +- pkg/sql/BUILD.bazel | 1 + pkg/sql/catalog/colinfo/result_columns.go | 3 +- pkg/sql/catalog/descpb/tenant.proto | 17 +++ pkg/sql/drop_tenant.go | 2 +- pkg/sql/faketreeeval/evalctx.go | 2 +- pkg/sql/gcjob_test/gc_job_test.go | 2 +- pkg/sql/logictest/testdata/logic_test/tenant | 102 +++++++++--------- .../testdata/logic_test/tenant_builtins | 30 +++--- pkg/sql/opaque.go | 3 + pkg/sql/parser/help_test.go | 3 + pkg/sql/parser/sql.y | 46 +++++++- pkg/sql/parser/testdata/alter_tenant | 24 +++++ pkg/sql/rename_tenant.go | 2 +- pkg/sql/sem/builtins/builtins.go | 17 ++- pkg/sql/sem/eval/deps.go | 2 +- pkg/sql/sem/tree/alter_tenant.go | 34 ++++++ pkg/sql/sem/tree/stmt.go | 10 ++ pkg/sql/sem/tree/walk.go | 20 ++++ pkg/sql/show_tenant.go | 1 + pkg/sql/tenant_creation.go | 18 +++- pkg/sql/tenant_deletion.go | 14 ++- pkg/sql/tenant_service.go | 68 ++++++++++++ pkg/sql/tenant_test.go | 1 + pkg/sql/tenant_update.go | 81 ++++++++++---- pkg/sql/walk.go | 2 + 35 files changed, 467 insertions(+), 153 deletions(-) create mode 100644 pkg/sql/tenant_service.go diff --git a/docs/generated/sql/bnf/stmt_block.bnf b/docs/generated/sql/bnf/stmt_block.bnf index 62735d5dc9be..ab02f9588e1c 100644 --- a/docs/generated/sql/bnf/stmt_block.bnf +++ b/docs/generated/sql/bnf/stmt_block.bnf @@ -1338,11 +1338,13 @@ unreserved_keyword ::= | 'SEQUENCE' | 'SEQUENCES' | 'SERVER' + | 'SERVICE' | 'SESSION' | 'SESSIONS' | 'SET' | 'SETS' | 'SHARE' + | 'SHARED' | 'SHOW' | 'SIMPLE' | 'SKIP' @@ -1361,6 +1363,7 @@ unreserved_keyword ::= | 'STATEMENTS' | 'STATISTICS' | 'STDIN' + | 'STOP' | 'STORAGE' | 'STORE' | 'STORED' diff --git a/pkg/ccl/backupccl/backup_test.go b/pkg/ccl/backupccl/backup_test.go index 40e4f6abcfe4..60955fcb0be0 100644 --- a/pkg/ccl/backupccl/backup_test.go +++ b/pkg/ccl/backupccl/backup_test.go @@ -6885,7 +6885,7 @@ func TestBackupRestoreTenant(t *testing.T) { {`1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) @@ -6896,13 +6896,13 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `true`, `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) @@ -6936,13 +6936,13 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `false`, `NULL`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-10", "id": "10", "name": "", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-10", "id": "10", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) @@ -6971,13 +6971,13 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `true`, `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) @@ -7006,7 +7006,7 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) @@ -7017,13 +7017,13 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `true`, `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) @@ -7050,7 +7050,7 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/clusterwide'`) @@ -7061,13 +7061,13 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `true`, `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) @@ -7105,7 +7105,7 @@ func TestBackupRestoreTenant(t *testing.T) { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, }) restoreDB.Exec(t, `RESTORE FROM 'nodelocal://1/clusterwide'`) @@ -7115,25 +7115,25 @@ func TestBackupRestoreTenant(t *testing.T) { { `1`, `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"}`, }, { `10`, `true`, `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-10", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, { `11`, `true`, `tenant-11`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "11", "name": "tenant-11", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "11", "name": "tenant-11", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, { `20`, `true`, `tenant-20`, - `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "20", "name": "tenant-20", "tenantReplicationJobId": "0"}`, + `{"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "20", "name": "tenant-20", "serviceMode": "NONE", "tenantReplicationJobId": "0"}`, }, }, ) diff --git a/pkg/ccl/backupccl/restore_job.go b/pkg/ccl/backupccl/restore_job.go index 669b337667b9..8d9a292deb7b 100644 --- a/pkg/ccl/backupccl/restore_job.go +++ b/pkg/ccl/backupccl/restore_job.go @@ -1198,18 +1198,19 @@ func createImportingDescriptors( if err != nil { return err } - for _, tenant := range details.Tenants { - switch tenant.DataState { + for _, tenantInfoCopy := range details.Tenants { + switch tenantInfoCopy.DataState { case descpb.TenantInfo_READY: // If the tenant was backed up in the `READY` state then we create // the restored record in an `ADD` state and mark it `READY` at // the end of the restore. - tenant.DataState = descpb.TenantInfo_ADD + tenantInfoCopy.ServiceMode = descpb.TenantInfo_NONE + tenantInfoCopy.DataState = descpb.TenantInfo_ADD case descpb.TenantInfo_DROP, descpb.TenantInfo_ADD: // If the tenant was backed up in a `DROP` or `ADD` state then we must // create the restored tenant record in that state as well. default: - return errors.AssertionFailedf("unknown tenant data state %v", tenant) + return errors.AssertionFailedf("unknown tenant data state %v", tenantInfoCopy) } spanConfigs := p.ExecCfg().SpanConfigKVAccessor.WithTxn(ctx, txn.KV()) if _, err := sql.CreateTenantRecord( @@ -1218,7 +1219,7 @@ func createImportingDescriptors( p.ExecCfg().Settings, txn, spanConfigs, - &tenant, + &tenantInfoCopy, initialTenantZoneConfig, ); err != nil { return err @@ -2197,7 +2198,7 @@ func (r *restoreResumer) publishDescriptors( // the tenant as the final step of the restore. The tenant has already // been created at an earlier stage in the restore in an `ADD` state. if err := sql.ActivateTenant( - ctx, r.execCfg.Settings, r.execCfg.Codec, txn, tenant.ID, + ctx, r.execCfg.Settings, r.execCfg.Codec, txn, tenant.ID, tenant.ServiceMode, ); err != nil { return err } diff --git a/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants b/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants index 4d492962e230..f7a29717806e 100644 --- a/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants +++ b/pkg/ccl/backupccl/testdata/backup-restore/restore-tenants @@ -15,15 +15,15 @@ SELECT crdb_internal.create_tenant(6); # Drop one of them. exec-sql -DROP TENANT [5] +ALTER TENANT [5] STOP SERVICE; DROP TENANT [5] ---- query-sql SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "serviceMode": "EXTERNAL", "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 {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} exec-sql expect-error-regex=(tenant 6 already exists) RESTORE TENANT 6 FROM LATEST IN 'nodelocal://1/tenant6'; @@ -75,7 +75,7 @@ RESTORE TENANT 6 FROM LATEST IN 'nodelocal://1/tenant6' WITH tenant_name = 'newn query-sql SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "newname", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "newname", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +5 false {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +6 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "6", "name": "tenant-6", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} diff --git a/pkg/ccl/logictestccl/testdata/logic_test/tenant_usage b/pkg/ccl/logictestccl/testdata/logic_test/tenant_usage index de87c68c971f..ba4b826bb912 100644 --- a/pkg/ccl/logictestccl/testdata/logic_test/tenant_usage +++ b/pkg/ccl/logictestccl/testdata/logic_test/tenant_usage @@ -20,6 +20,7 @@ SELECT crdb_internal.update_tenant_resource_limits(5, 1000, 100, 0, now(), 0) # Note this marks the tenant as dropped. The GC will not delete the tenant # until after the ttl expires. statement ok +ALTER TENANT [5] STOP SERVICE; DROP TENANT [5] query error tenant "5" is not active diff --git a/pkg/ccl/sqlproxyccl/tenant/directory_cache_test.go b/pkg/ccl/sqlproxyccl/tenant/directory_cache_test.go index 312dd33e9ba7..0d34b9943cfd 100644 --- a/pkg/ccl/sqlproxyccl/tenant/directory_cache_test.go +++ b/pkg/ccl/sqlproxyccl/tenant/directory_cache_test.go @@ -405,14 +405,19 @@ func createTenant(tc serverutils.TestClusterInterface, id roachpb.TenantID) erro func destroyTenant(tc serverutils.TestClusterInterface, id roachpb.TenantID) error { srv := tc.Server(0) conn := srv.InternalExecutor().(*sql.InternalExecutor) - if _, err := conn.Exec( - context.Background(), - "testserver-destroy-tenant", - nil, /* txn */ - "DROP TENANT [$1] IMMEDIATE", - id.ToUint64(), - ); err != nil { - return err + for _, stmt := range []string{ + `ALTER TENANT [$1] STOP SERVICE`, + `DROP TENANT [$1] IMMEDIATE`, + } { + if _, err := conn.Exec( + context.Background(), + "testserver-destroy-tenant", + nil, /* txn */ + stmt, + id.ToUint64(), + ); err != nil { + return err + } } return nil } diff --git a/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go b/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go index 91bfe2021091..63c8b880d1a5 100644 --- a/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go +++ b/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go @@ -925,6 +925,7 @@ func TestTenantStreamingShowTenant(t *testing.T) { id int dest string status string + serviceMode string source string sourceUri string jobId int @@ -933,10 +934,11 @@ func TestTenantStreamingShowTenant(t *testing.T) { cutoverTime []byte // should be nil ) row := c.DestSysSQL.QueryRow(t, fmt.Sprintf("SHOW TENANT %s WITH REPLICATION STATUS", args.DestTenantName)) - row.Scan(&id, &dest, &status, &source, &sourceUri, &jobId, &maxReplTime, &protectedTime, &cutoverTime) + row.Scan(&id, &dest, &status, &serviceMode, &source, &sourceUri, &jobId, &maxReplTime, &protectedTime, &cutoverTime) require.Equal(t, 2, id) require.Equal(t, "destination", dest) require.Equal(t, "REPLICATING", status) + require.Equal(t, "NONE", serviceMode) require.Equal(t, "source", source) require.Equal(t, c.SrcURL.String(), sourceUri) require.Equal(t, ingestionJobID, jobId) diff --git a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go index 31225fd2d02b..c14f7f6e5bdf 100644 --- a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go +++ b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go @@ -149,13 +149,14 @@ func ingestionPlanHook( sourceTenant, dstTenantName, dstTenantID) } - // Create a new tenant for the replication stream + // Create a new tenant for the replication stream. jobID := p.ExecCfg().JobRegistry.MakeJobID() tenantInfo := &descpb.TenantInfoWithUsage{ TenantInfo: descpb.TenantInfo{ // dstTenantID may be zero which will cause auto-allocation. ID: dstTenantID, DataState: descpb.TenantInfo_ADD, + ServiceMode: descpb.TenantInfo_NONE, Name: roachpb.TenantName(dstTenantName), TenantReplicationJobID: jobID, }, diff --git a/pkg/ccl/streamingccl/streamingest/testdata/simple b/pkg/ccl/streamingccl/streamingest/testdata/simple index 85878eae177d..0f0eed88197e 100644 --- a/pkg/ccl/streamingccl/streamingest/testdata/simple +++ b/pkg/ccl/streamingccl/streamingest/testdata/simple @@ -28,14 +28,14 @@ IMPORT INTO d.x CSV DATA ('userfile:///dx/export*-n*.0.csv'); query-sql as=source-system SHOW TENANTS ---- -1 system READY -10 source READY +1 system READY EXTERNAL +10 source READY NONE query-sql as=destination-system SHOW TENANTS ---- -1 system READY -2 destination REPLICATING +1 system READY EXTERNAL +2 destination REPLICATING NONE let $ts as=source-system SELECT clock_timestamp()::timestamp::string diff --git a/pkg/ccl/testccl/sqlccl/tenant_gc_test.go b/pkg/ccl/testccl/sqlccl/tenant_gc_test.go index 8a86c8fc5413..0dc3ae258683 100644 --- a/pkg/ccl/testccl/sqlccl/tenant_gc_test.go +++ b/pkg/ccl/testccl/sqlccl/tenant_gc_test.go @@ -121,7 +121,9 @@ func TestGCTenantRemovesSpanConfigs(t *testing.T) { ) error { return sql.TestingUpdateTenantRecord( ctx, ts.ClusterSettings(), txn, - &descpb.TenantInfo{ID: tenantID.ToUint64(), DataState: descpb.TenantInfo_DROP}, + &descpb.TenantInfo{ID: tenantID.ToUint64(), + ServiceMode: descpb.TenantInfo_NONE, + DataState: descpb.TenantInfo_DROP}, ) })) diff --git a/pkg/sql/BUILD.bazel b/pkg/sql/BUILD.bazel index 092775939373..1c2f7eb5eff7 100644 --- a/pkg/sql/BUILD.bazel +++ b/pkg/sql/BUILD.bazel @@ -244,6 +244,7 @@ go_library( "tenant_creation.go", "tenant_deletion.go", "tenant_gc.go", + "tenant_service.go", "tenant_settings.go", "tenant_spec.go", "tenant_update.go", diff --git a/pkg/sql/catalog/colinfo/result_columns.go b/pkg/sql/catalog/colinfo/result_columns.go index 3f75744c281f..a1c70b37bed8 100644 --- a/pkg/sql/catalog/colinfo/result_columns.go +++ b/pkg/sql/catalog/colinfo/result_columns.go @@ -279,7 +279,8 @@ var ExportColumns = ResultColumns{ var TenantColumns = ResultColumns{ {Name: "id", Typ: types.Int}, {Name: "name", Typ: types.String}, - {Name: "status", Typ: types.String}, + {Name: "data_status", Typ: types.String}, + {Name: "service_mode", Typ: types.String}, } // TenantColumnsWithReplication is appended to TenantColumns for diff --git a/pkg/sql/catalog/descpb/tenant.proto b/pkg/sql/catalog/descpb/tenant.proto index 98c0ddfb3ace..ffaf0979da05 100644 --- a/pkg/sql/catalog/descpb/tenant.proto +++ b/pkg/sql/catalog/descpb/tenant.proto @@ -32,6 +32,20 @@ message TenantInfo { DROP = 2; } + // The way the tenant can be served to clients. + enum ServiceMode { + // Service allowed using separate processes. + // This is the default value for backward-compatibility with + // records created for CockroachCloud Serverless pre-v23.1. + EXTERNAL = 0; + // No service allowed. + NONE = 1; + // Service allowed using shared-process multitenancy. + // This mode causes KV nodes to spontaneously start the SQL service + // for the tenant. + SHARED = 2; + } + // ID is the internal numeric identifier of the tenant. // It remains mostly invisible to clients. optional uint64 id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "ID"]; @@ -65,6 +79,9 @@ message TenantInfo { optional cockroach.multitenant.tenantcapabilitiespb.TenantCapabilities capabilities = 6 [ (gogoproto.nullable) = false ]; + + // ServiceMode determines how the tenant should be served. + optional ServiceMode service_mode = 7 [(gogoproto.nullable) = false]; } // TenantInfoAndUsage contains the information for a tenant in a multi-tenant diff --git a/pkg/sql/drop_tenant.go b/pkg/sql/drop_tenant.go index cc220bc6760a..93b20db14f4a 100644 --- a/pkg/sql/drop_tenant.go +++ b/pkg/sql/drop_tenant.go @@ -51,7 +51,7 @@ func (n *dropTenantNode) startExec(params runParams) error { } return err } - return params.p.DropTenantByID(params.ctx, tenInfo.ID, n.immediate) + return params.p.DropTenantByID(params.ctx, tenInfo.ID, n.immediate, false /* ignoreServiceMode */) } func (n *dropTenantNode) Next(_ runParams) (bool, error) { return false, nil } diff --git a/pkg/sql/faketreeeval/evalctx.go b/pkg/sql/faketreeeval/evalctx.go index 29c1ca3a830a..7a8b78060e5d 100644 --- a/pkg/sql/faketreeeval/evalctx.go +++ b/pkg/sql/faketreeeval/evalctx.go @@ -547,7 +547,7 @@ func (c *DummyTenantOperator) LookupTenantID( // DropTenantByID is part of the tree.TenantOperator interface. func (c *DummyTenantOperator) DropTenantByID( - ctx context.Context, tenantID uint64, synchronous bool, + ctx context.Context, tenantID uint64, synchronous, ignoreServiceMode bool, ) error { return errors.WithStack(errEvalTenant) } diff --git a/pkg/sql/gcjob_test/gc_job_test.go b/pkg/sql/gcjob_test/gc_job_test.go index 4b95b05b6c94..82977a7bcf00 100644 --- a/pkg/sql/gcjob_test/gc_job_test.go +++ b/pkg/sql/gcjob_test/gc_job_test.go @@ -493,7 +493,7 @@ func TestGCTenant(t *testing.T) { require.EqualError( t, gcClosure(dropTenID, progress), - `GC state for tenant id:11 data_state:DROP name:"tenant-11" dropped_name:"" tenant_replication_job_id:0 capabilities:<> is DELETED yet the tenant row still exists`, + `GC state for tenant id:11 data_state:DROP name:"tenant-11" dropped_name:"" tenant_replication_job_id:0 capabilities:<> service_mode:EXTERNAL 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 03988c096db2..28a2095c2490 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 18 system +1 true 20 system # Create a few tenants. @@ -38,49 +38,49 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true tenant-one {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-one", "tenantReplicationJobId": "0"} -3 true two {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "3", "name": "two", "tenantReplicationJobId": "0"} -4 true three {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "4", "name": "three", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-one", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "3", "name": "two", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "4", "name": "three", "serviceMode": "NONE", "tenantReplicationJobId": "0"} -query ITT colnames +query ITTT colnames SHOW TENANT system ---- -id name status -1 system READY +id name data_status service_mode +1 system READY EXTERNAL -query ITT colnames +query ITTT colnames SHOW TENANT "tenant-one" ---- -id name status -2 tenant-one READY +id name data_status service_mode +2 tenant-one READY NONE -query ITT colnames +query ITTT colnames SHOW TENANT "two" ---- -id name status -3 two READY +id name data_status service_mode +3 two READY NONE -query ITT colnames +query ITTT colnames SHOW TENANT two ---- -id name status -3 two READY +id name data_status service_mode +3 two READY NONE -query ITT colnames +query ITTT colnames SHOW TENANT three ---- -id name status -4 three READY +id name data_status service_mode +4 three READY NONE -query ITT colnames +query ITTT colnames SHOW TENANTS ---- -id name status -1 system READY -2 tenant-one READY -3 two READY -4 three READY +id name data_status service_mode +1 system READY EXTERNAL +2 tenant-one READY NONE +3 two READY NONE +4 three READY NONE statement error tenant name cannot be empty ALTER TENANT [4] RENAME TO "" @@ -97,20 +97,20 @@ ALTER TENANT [4] RENAME TO blux statement ok ALTER TENANT blux RENAME TO 'blix' -query ITT colnames +query ITTT colnames SELECT * FROM [SHOW TENANTS] WHERE id = 4 ---- -id name status -4 blix READY +id name data_status service_mode +4 blix READY NONE statement ok ALTER TENANT blix RENAME TO three -query ITT colnames +query ITTT colnames SELECT * FROM [SHOW TENANTS] WHERE id = 4 ---- -id name status -4 three READY +id name data_status service_mode +4 three READY NONE statement error tenant "seven" does not exist SHOW TENANT seven @@ -158,7 +158,7 @@ FROM system.tenants WHERE name = 'four' ORDER BY id ---- id active name crdb_internal.pb_to_json -5 true four {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "5", "name": "four", "tenantReplicationJobId": "0"} +5 true four {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "5", "name": "four", "serviceMode": "NONE", "tenantReplicationJobId": "0"} statement ok DROP TENANT four @@ -232,14 +232,14 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true tenant-one {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-one", "tenantReplicationJobId": "0"} -3 true two {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "3", "name": "two", "tenantReplicationJobId": "0"} -4 true three {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "4", "name": "three", "tenantReplicationJobId": "0"} -5 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "four", "id": "5", "name": "", "tenantReplicationJobId": "0"} -6 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "five-requiring-quotes", "id": "6", "name": "", "tenantReplicationJobId": "0"} -7 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "to-be-reclaimed", "id": "7", "name": "", "tenantReplicationJobId": "0"} -8 true to-be-reclaimed {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "8", "name": "to-be-reclaimed", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-one", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "3", "name": "two", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "4", "name": "three", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +5 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "four", "id": "5", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +6 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "five-requiring-quotes", "id": "6", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +7 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "to-be-reclaimed", "id": "7", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +8 true to-be-reclaimed {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "8", "name": "to-be-reclaimed", "serviceMode": "NONE", "tenantReplicationJobId": "0"} # More valid tenant names. statement ok @@ -247,15 +247,15 @@ CREATE TENANT "1"; CREATE TENANT "a-b"; CREATE TENANT "hello-100" -query ITT colnames +query ITTT colnames SHOW TENANTS ---- -id name status -1 system READY -2 tenant-one READY -3 two READY -4 three READY -8 to-be-reclaimed READY -9 1 READY -10 a-b READY -11 hello-100 READY +id name data_status service_mode +1 system READY EXTERNAL +2 tenant-one READY NONE +3 two READY NONE +4 three READY NONE +8 to-be-reclaimed READY NONE +9 1 READY NONE +10 a-b READY NONE +11 hello-100 READY NONE diff --git a/pkg/sql/logictest/testdata/logic_test/tenant_builtins b/pkg/sql/logictest/testdata/logic_test/tenant_builtins index ad4355432286..768eed817778 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 18 system +1 true 20 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 {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "tenantReplicationJobId": "0"} -5 true tenant-5 {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "5", "name": "tenant-5", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +5 true tenant-5 {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "5", "name": "tenant-5", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "serviceMode": "NONE", "tenantReplicationJobId": "0"} # Check we can add a name where none existed before. statement ok @@ -96,6 +96,12 @@ id active name query error tenant 5 is not in data state DROP SELECT crdb_internal.gc_tenant(5) +statement error cannot drop tenant "my-new-tenant-name" \(5\) in service mode EXTERNAL +DROP TENANT [5] + +statement ok +ALTER TENANT [5] STOP SERVICE + # Note this just marks the tenant as dropped but does not call GC. statement ok @@ -107,10 +113,10 @@ FROM system.tenants ORDER BY id ---- id active name crdb_internal.pb_to_json -1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "tenantReplicationJobId": "0"} -5 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "my-new-tenant-name", "id": "5", "name": "", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +5 false NULL {"capabilities": {"canAdminSplit": false}, "dataState": "DROP", "droppedName": "my-new-tenant-name", "id": "5", "name": "", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "serviceMode": "NONE", "tenantReplicationJobId": "0"} # Try to recreate an existing tenant. @@ -217,9 +223,9 @@ FROM system.tenants ORDER BY id ---- id active crdb_internal.pb_to_json -1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "tenantReplicationJobId": "0"} -2 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "tenantReplicationJobId": "0"} -10 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "tenantReplicationJobId": "0"} +1 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "1", "name": "system", "serviceMode": "EXTERNAL", "tenantReplicationJobId": "0"} +2 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "2", "name": "tenant-number-eleven", "serviceMode": "NONE", "tenantReplicationJobId": "0"} +10 true {"capabilities": {"canAdminSplit": false}, "dataState": "READY", "droppedName": "", "id": "10", "name": "tenant-number-ten", "serviceMode": "NONE", "tenantReplicationJobId": "0"} query error tenant resource limits require a CCL binary SELECT crdb_internal.update_tenant_resource_limits(10, 1000, 100, 0, now(), 0) diff --git a/pkg/sql/opaque.go b/pkg/sql/opaque.go index 62077f4644dd..1d66a09f32f4 100644 --- a/pkg/sql/opaque.go +++ b/pkg/sql/opaque.go @@ -132,6 +132,8 @@ func planOpaque(ctx context.Context, p *planner, stmt tree.Statement) (planNode, return p.AlterTenantSetClusterSetting(ctx, n) case *tree.AlterTenantRename: return p.alterRenameTenant(ctx, n) + case *tree.AlterTenantService: + return p.alterTenantService(ctx, n) case *tree.AlterType: return p.AlterType(ctx, n) case *tree.AlterRole: @@ -314,6 +316,7 @@ func init() { &tree.AlterTableSetSchema{}, &tree.AlterTenantRename{}, &tree.AlterTenantSetClusterSetting{}, + &tree.AlterTenantService{}, &tree.AlterType{}, &tree.AlterSequence{}, &tree.AlterRole{}, diff --git a/pkg/sql/parser/help_test.go b/pkg/sql/parser/help_test.go index 4f5531492265..0ff8fec6a189 100644 --- a/pkg/sql/parser/help_test.go +++ b/pkg/sql/parser/help_test.go @@ -63,6 +63,9 @@ func TestContextualHelp(t *testing.T) { {`ALTER TENANT foo RENAME TO bar ??`, `ALTER TENANT RENAME`}, + {`ALTER TENANT foo START SERVICE ??`, `ALTER TENANT SERVICE`}, + {`ALTER TENANT foo STOP ??`, `ALTER TENANT SERVICE`}, + {`ALTER TYPE ??`, `ALTER TYPE`}, {`ALTER TYPE t ??`, `ALTER TYPE`}, {`ALTER TYPE t ADD VALUE ??`, `ALTER TYPE`}, diff --git a/pkg/sql/parser/sql.y b/pkg/sql/parser/sql.y index 334c9d79f92c..cfe644a88526 100644 --- a/pkg/sql/parser/sql.y +++ b/pkg/sql/parser/sql.y @@ -949,12 +949,12 @@ func (u *sqlSymUnion) cteMaterializeClause() tree.CTEMaterializeClause { %token SAVEPOINT SCANS SCATTER SCHEDULE SCHEDULES SCROLL SCHEMA SCHEMA_ONLY SCHEMAS SCRUB %token SEARCH SECOND SECONDARY SECURITY SELECT SEQUENCE SEQUENCES -%token SERIALIZABLE SERVER SESSION SESSIONS SESSION_USER SET SETOF SETS SETTING SETTINGS -%token SHARE SHOW SIMILAR SIMPLE SKIP SKIP_LOCALITIES_CHECK SKIP_MISSING_FOREIGN_KEYS +%token SERIALIZABLE SERVER SERVICE SESSION SESSIONS SESSION_USER SET SETOF SETS SETTING SETTINGS +%token SHARE SHARED SHOW SIMILAR SIMPLE SKIP SKIP_LOCALITIES_CHECK SKIP_MISSING_FOREIGN_KEYS %token SKIP_MISSING_SEQUENCES SKIP_MISSING_SEQUENCE_OWNERS SKIP_MISSING_VIEWS SMALLINT SMALLSERIAL SNAPSHOT SOME SPLIT SQL %token SQLLOGIN -%token STABLE START STATE STATISTICS STATUS STDIN STREAM STRICT STRING STORAGE STORE STORED STORING SUBSTRING SUPER +%token STABLE START STATE STATISTICS STATUS STDIN STREAM STRICT STRING STOP STORAGE STORE STORED STORING SUBSTRING SUPER %token SUPPORT SURVIVE SURVIVAL SYMMETRIC SYNTAX SYSTEM SQRT SUBSCRIPTION STATEMENTS %token TABLE TABLES TABLESPACE TEMP TEMPLATE TEMPORARY TENANT TENANT_NAME TENANTS TESTING_RELOCATE TEXT THEN @@ -1049,6 +1049,7 @@ func (u *sqlSymUnion) cteMaterializeClause() tree.CTEMaterializeClause { // Other ALTER TENANT statements. %type alter_tenant_replication_stmt %type alter_tenant_rename_stmt +%type alter_tenant_service_stmt // ALTER PARTITION %type alter_zone_partition_stmt @@ -6179,6 +6180,7 @@ alter_tenant_stmt: alter_tenant_replication_stmt // EXTEND WITH HELP: ALTER TENANT REPLICATION | alter_tenant_csetting_stmt // EXTEND WITH HELP: ALTER TENANT CLUSTER SETTING | alter_tenant_rename_stmt // EXTEND WITH HELP: ALTER TENANT RENAME +| alter_tenant_service_stmt // EXTEND WITH HELP: ALTER TENANT SERVICE | ALTER TENANT error // SHOW HELP: ALTER TENANT tenant_spec: @@ -6201,6 +6203,41 @@ alter_tenant_rename_stmt: } } +// %Help: ALTER TENANT SERVICE - alter tenant service mode +// %Category: Experimental +// %Text: +// ALTER TENANT START SERVICE EXTERNAL +// ALTER TENANT START SERVICE SHARED +// ALTER TENANT STOP SERVICE +alter_tenant_service_stmt: + ALTER TENANT tenant_spec START SERVICE EXTERNAL + { + /* SKIP DOC */ + $$.val = &tree.AlterTenantService{ + TenantSpec: $3.tenantSpec(), + Command: tree.TenantStartServiceExternal, + } + } +| ALTER TENANT tenant_spec START SERVICE SHARED + { + /* SKIP DOC */ + $$.val = &tree.AlterTenantService{ + TenantSpec: $3.tenantSpec(), + Command: tree.TenantStartServiceShared, + } + } +| ALTER TENANT tenant_spec STOP SERVICE + { + /* SKIP DOC */ + $$.val = &tree.AlterTenantService{ + TenantSpec: $3.tenantSpec(), + Command: tree.TenantStopService, + } + } +| ALTER TENANT tenant_spec START error // SHOW HELP: ALTER TENANT SERVICE +| ALTER TENANT tenant_spec STOP error // SHOW HELP: ALTER TENANT SERVICE + + // %Help: ALTER TENANT REPLICATION - alter tenant replication stream // %Category: Experimental // %Text: @@ -16054,11 +16091,13 @@ unreserved_keyword: | SEQUENCE | SEQUENCES | SERVER +| SERVICE | SESSION | SESSIONS | SET | SETS | SHARE +| SHARED | SHOW | SIMPLE | SKIP @@ -16077,6 +16116,7 @@ unreserved_keyword: | STATEMENTS | STATISTICS | STDIN +| STOP | STORAGE | STORE | STORED diff --git a/pkg/sql/parser/testdata/alter_tenant b/pkg/sql/parser/testdata/alter_tenant index ba86731fcc6f..0619bc954968 100644 --- a/pkg/sql/parser/testdata/alter_tenant +++ b/pkg/sql/parser/testdata/alter_tenant @@ -190,3 +190,27 @@ ALTER TENANT 'foo' RENAME TO bar ALTER TENANT ('foo') RENAME TO (bar) -- fully parenthesized ALTER TENANT '_' RENAME TO bar -- literals removed ALTER TENANT 'foo' RENAME TO _ -- identifiers removed + +parse +ALTER TENANT 'foo' START SERVICE EXTERNAL +---- +ALTER TENANT 'foo' START SERVICE EXTERNAL +ALTER TENANT ('foo') START SERVICE EXTERNAL -- fully parenthesized +ALTER TENANT '_' START SERVICE EXTERNAL -- literals removed +ALTER TENANT 'foo' START SERVICE EXTERNAL -- identifiers removed + +parse +ALTER TENANT 'foo' START SERVICE SHARED +---- +ALTER TENANT 'foo' START SERVICE SHARED +ALTER TENANT ('foo') START SERVICE SHARED -- fully parenthesized +ALTER TENANT '_' START SERVICE SHARED -- literals removed +ALTER TENANT 'foo' START SERVICE SHARED -- identifiers removed + +parse +ALTER TENANT 'foo' STOP SERVICE +---- +ALTER TENANT 'foo' STOP SERVICE +ALTER TENANT ('foo') STOP SERVICE -- fully parenthesized +ALTER TENANT '_' STOP SERVICE -- literals removed +ALTER TENANT 'foo' STOP SERVICE -- identifiers removed diff --git a/pkg/sql/rename_tenant.go b/pkg/sql/rename_tenant.go index 2b74e0469005..f45b033558fb 100644 --- a/pkg/sql/rename_tenant.go +++ b/pkg/sql/rename_tenant.go @@ -69,7 +69,7 @@ func (n *renameTenantNode) startExec(params runParams) error { if err != nil { return err } - return params.p.renameTenant(params.ctx, rec.ID, newName) + return params.p.renameTenant(params.ctx, rec, newName) } func (n *renameTenantNode) Next(_ runParams) (bool, error) { return false, nil } diff --git a/pkg/sql/sem/builtins/builtins.go b/pkg/sql/sem/builtins/builtins.go index acb682d1e16c..ee9436a0c3ce 100644 --- a/pkg/sql/sem/builtins/builtins.go +++ b/pkg/sql/sem/builtins/builtins.go @@ -4931,16 +4931,20 @@ value if you rely on the HLC for accuracy.`, `Must be run by the system tenant.`, Volatility: volatility.Volatile, }, + // This overload is provided for compatibility with CC Serverless + // v22.2 and previous versions. tree.Overload{ Types: tree.ParamTypes{ {Name: "id", Typ: types.Int}, }, ReturnType: tree.FixedReturnType(types.Int), IsUDF: true, - Body: `SELECT crdb_internal.create_tenant(json_build_object('id', $1))`, - Info: `create_tenant(id) is an alias for create_tenant('{"id": id}'::jsonb)`, + Body: `SELECT crdb_internal.create_tenant(json_build_object('id', $1, 'service_mode', + 'external'))`, + Info: `create_tenant(id) is an alias for create_tenant('{"id": id, "service_mode": "external"}'::jsonb)`, Volatility: volatility.Volatile, }, + // This overload is provided for use in tests. tree.Overload{ Types: tree.ParamTypes{ {Name: "id", Typ: types.Int}, @@ -4952,6 +4956,7 @@ value if you rely on the HLC for accuracy.`, Info: `create_tenant(id, name) is an alias for create_tenant('{"id": id, "name": name}'::jsonb)`, Volatility: volatility.Volatile, }, + // This overload is deprecated. Use CREATE TENANT instead. tree.Overload{ Types: tree.ParamTypes{ {Name: "name", Typ: types.String}, @@ -4959,7 +4964,8 @@ value if you rely on the HLC for accuracy.`, ReturnType: tree.FixedReturnType(types.Int), IsUDF: true, Body: `SELECT crdb_internal.create_tenant(json_build_object('name', $1))`, - Info: `create_tenant(name) is an alias for create_tenant('{"name": name}'::jsonb)`, + Info: `create_tenant(name) is an alias for create_tenant('{"name": name}'::jsonb). +DO NOT USE -- USE 'CREATE TENANT' INSTEAD`, Volatility: volatility.Volatile, }, ), @@ -5026,8 +5032,11 @@ value if you rely on the HLC for accuracy.`, return nil, err } synchronous := tree.MustBeDBool(args[1]) + + // Note: we pass true to ignoreServiceMode for compatibility + // with CC Serverless pre-v23.1. if err := evalCtx.Tenant.DropTenantByID( - ctx, uint64(sTenID), bool(synchronous), + ctx, uint64(sTenID), bool(synchronous), true, /* ignoreServiceMode */ ); err != nil { return nil, err } diff --git a/pkg/sql/sem/eval/deps.go b/pkg/sql/sem/eval/deps.go index 1ec74f89b33c..70f77717198e 100644 --- a/pkg/sql/sem/eval/deps.go +++ b/pkg/sql/sem/eval/deps.go @@ -545,7 +545,7 @@ type TenantOperator interface { // DropTenantByID attempts to uninstall an existing tenant from the system. // It returns an error if the tenant does not exist. If synchronous is true // the gc job will not wait for a GC ttl. - DropTenantByID(ctx context.Context, tenantID uint64, synchronous bool) error + DropTenantByID(ctx context.Context, tenantID uint64, synchronous, ignoreServiceMode bool) error // GCTenant attempts to garbage collect a DROP tenant from the system. Upon // success it also removes the tenant record. diff --git a/pkg/sql/sem/tree/alter_tenant.go b/pkg/sql/sem/tree/alter_tenant.go index c0329f241a6c..6dd56a603dec 100644 --- a/pkg/sql/sem/tree/alter_tenant.go +++ b/pkg/sql/sem/tree/alter_tenant.go @@ -83,3 +83,37 @@ func (n *AlterTenantRename) Format(ctx *FmtCtx) { ctx.WriteString(" RENAME TO ") ctx.FormatNode(n.NewName) } + +// AlterTenantService represents an ALTER TENANT START/STOP SERVICE statement. +type AlterTenantService struct { + TenantSpec *TenantSpec + Command TenantServiceCmd +} + +// TenantServiceCmd represents a parameter to ALTER TENANT. +type TenantServiceCmd int8 + +const ( + // TenantStartServiceExternal encodes START SERVICE EXTERNAL. + TenantStartServiceExternal TenantServiceCmd = 0 + // TenantStartServiceExternal encodes START SERVICE SHARED. + TenantStartServiceShared TenantServiceCmd = 1 + // TenantStartServiceExternal encodes STOP SERVICE. + TenantStopService TenantServiceCmd = 2 +) + +var _ Statement = &AlterTenantService{} + +// Format implements the NodeFormatter interface. +func (n *AlterTenantService) Format(ctx *FmtCtx) { + ctx.WriteString("ALTER TENANT ") + ctx.FormatNode(n.TenantSpec) + switch n.Command { + case TenantStartServiceExternal: + ctx.WriteString(" START SERVICE EXTERNAL") + case TenantStartServiceShared: + ctx.WriteString(" START SERVICE SHARED") + case TenantStopService: + ctx.WriteString(" STOP SERVICE") + } +} diff --git a/pkg/sql/sem/tree/stmt.go b/pkg/sql/sem/tree/stmt.go index 6c6c35f718b5..e86e6cfeca50 100644 --- a/pkg/sql/sem/tree/stmt.go +++ b/pkg/sql/sem/tree/stmt.go @@ -469,6 +469,15 @@ func (*AlterTenantRename) StatementType() StatementType { return TypeDCL } // StatementTag returns a short string identifying the type of statement. func (*AlterTenantRename) StatementTag() string { return "ALTER TENANT RENAME" } +// StatementReturnType implements the Statement interface. +func (*AlterTenantService) StatementReturnType() StatementReturnType { return Ack } + +// StatementType implements the Statement interface. +func (*AlterTenantService) StatementType() StatementType { return TypeDCL } + +// StatementTag returns a short string identifying the type of statement. +func (*AlterTenantService) StatementTag() string { return "ALTER TENANT SERVICE" } + // StatementReturnType implements the Statement interface. func (*AlterType) StatementReturnType() StatementReturnType { return DDL } @@ -2114,6 +2123,7 @@ func (n *AlterTableSetSchema) String() string { return AsString( func (n *AlterTenantSetClusterSetting) String() string { return AsString(n) } func (n *AlterTenantRename) String() string { return AsString(n) } func (n *AlterTenantReplication) String() string { return AsString(n) } +func (n *AlterTenantService) String() string { return AsString(n) } func (n *AlterType) String() string { return AsString(n) } func (n *AlterRole) String() string { return AsString(n) } func (n *AlterRoleSet) String() string { return AsString(n) } diff --git a/pkg/sql/sem/tree/walk.go b/pkg/sql/sem/tree/walk.go index 1887a5de2ad2..126fe807293c 100644 --- a/pkg/sql/sem/tree/walk.go +++ b/pkg/sql/sem/tree/walk.go @@ -1088,6 +1088,25 @@ func (n *AlterTenantRename) walkStmt(v Visitor) Statement { return ret } +// copyNode makes a copy of this Statement without recursing in any child Statements. +func (n *AlterTenantService) copyNode() *AlterTenantService { + stmtCopy := *n + return &stmtCopy +} + +// walkStmt is part of the walkableStmt interface. +func (n *AlterTenantService) walkStmt(v Visitor) Statement { + ret := n + ts, changed := walkTenantSpec(v, n.TenantSpec) + if changed { + if ret == n { + ret = n.copyNode() + } + ret.TenantSpec = ts + } + return ret +} + // copyNode makes a copy of this Statement without recursing in any child Statements. func (n *DropTenant) copyNode() *DropTenant { stmtCopy := *n @@ -1814,6 +1833,7 @@ func (stmt *BeginTransaction) walkStmt(v Visitor) Statement { var _ walkableStmt = &AlterTenantRename{} var _ walkableStmt = &AlterTenantReplication{} +var _ walkableStmt = &AlterTenantService{} var _ walkableStmt = &AlterTenantSetClusterSetting{} var _ walkableStmt = &Backup{} var _ walkableStmt = &BeginTransaction{} diff --git a/pkg/sql/show_tenant.go b/pkg/sql/show_tenant.go index 76a4ad635e38..6df8d2a423ae 100644 --- a/pkg/sql/show_tenant.go +++ b/pkg/sql/show_tenant.go @@ -239,6 +239,7 @@ func (n *showTenantNode) Values() tree.Datums { tree.NewDInt(tree.DInt(tenantInfo.ID)), tree.NewDString(string(tenantInfo.Name)), tree.NewDString(string(v.tenantStatus)), + tree.NewDString(tenantInfo.ServiceMode.String()), } if n.withReplication { diff --git a/pkg/sql/tenant_creation.go b/pkg/sql/tenant_creation.go index 09bd75dee58a..bd8bb6043987 100644 --- a/pkg/sql/tenant_creation.go +++ b/pkg/sql/tenant_creation.go @@ -58,8 +58,9 @@ func (p *planner) CreateTenant( } type createTenantConfig struct { - ID *uint64 `json:"id,omitempty"` - Name *string `json:"name,omitempty"` + ID *uint64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + ServiceMode *string `json:"service_mode,omitempty"` } func (p *planner) createTenantInternal( @@ -73,6 +74,14 @@ func (p *planner) createTenantInternal( if ctcfg.Name != nil { name = roachpb.TenantName(*ctcfg.Name) } + serviceMode := descpb.TenantInfo_NONE + if ctcfg.ServiceMode != nil { + v, ok := descpb.TenantInfo_ServiceMode_value[strings.ToUpper(*ctcfg.ServiceMode)] + if !ok { + return tid, pgerror.Newf(pgcode.Syntax, "unknown service mode: %q", *ctcfg.ServiceMode) + } + serviceMode = descpb.TenantInfo_ServiceMode(v) + } // tenantID uint64, name roachpb.TenantName, if p.EvalContext().TxnReadOnly { @@ -91,8 +100,9 @@ func (p *planner) createTenantInternal( 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: descpb.TenantInfo_READY, - Name: name, + DataState: descpb.TenantInfo_READY, + Name: name, + ServiceMode: serviceMode, }, } diff --git a/pkg/sql/tenant_deletion.go b/pkg/sql/tenant_deletion.go index 23c4c1821751..b1a19688dd76 100644 --- a/pkg/sql/tenant_deletion.go +++ b/pkg/sql/tenant_deletion.go @@ -21,13 +21,15 @@ import ( "github.com/cockroachdb/cockroach/pkg/settings/cluster" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" "github.com/cockroachdb/cockroach/pkg/sql/isql" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" + "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" "github.com/cockroachdb/cockroach/pkg/util/timeutil" "github.com/cockroachdb/errors" ) // DropTenantByID implements the tree.TenantOperator interface. func (p *planner) DropTenantByID( - ctx context.Context, tenID uint64, synchronousImmediateDrop bool, + ctx context.Context, tenID uint64, synchronousImmediateDrop, ignoreServiceMode bool, ) error { if err := p.validateDropTenant(ctx); err != nil { return err @@ -46,6 +48,7 @@ func (p *planner) DropTenantByID( p.User(), info, synchronousImmediateDrop, + ignoreServiceMode, ) } @@ -70,6 +73,7 @@ func dropTenantInternal( user username.SQLUsername, info *descpb.TenantInfo, synchronousImmediateDrop bool, + ignoreServiceMode bool, ) error { const op = "destroy" tenID := info.ID @@ -77,8 +81,14 @@ func dropTenantInternal( return err } + if !ignoreServiceMode && info.ServiceMode != descpb.TenantInfo_NONE { + return errors.WithHint(pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "cannot drop tenant %q (%d) in service mode %v", info.Name, tenID, info.ServiceMode), + "Use ALTER TENANT STOP SERVICE before DROP TENANT.") + } + if info.DataState == descpb.TenantInfo_DROP { - return errors.Errorf("tenant %d is already in data state DROP", tenID) + return errors.Errorf("tenant %q (%d) is already in data state DROP", info.Name, tenID) } // Mark the tenant as dropping. diff --git a/pkg/sql/tenant_service.go b/pkg/sql/tenant_service.go new file mode 100644 index 000000000000..a4433a61d2a2 --- /dev/null +++ b/pkg/sql/tenant_service.go @@ -0,0 +1,68 @@ +// 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. + +package sql + +import ( + "context" + + "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" + "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" + "github.com/cockroachdb/errors" +) + +type alterTenantServiceNode struct { + tenantSpec tenantSpec + newMode descpb.TenantInfo_ServiceMode +} + +func (p *planner) alterTenantService( + ctx context.Context, n *tree.AlterTenantService, +) (planNode, error) { + // Even though the call to Update in startExec also + // performs this check, we need to do this early because otherwise + // the lookup of the ID from the name will fail. + if err := rejectIfCantCoordinateMultiTenancy(p.execCfg.Codec, "set tenant service"); err != nil { + return nil, err + } + + var newMode descpb.TenantInfo_ServiceMode + switch n.Command { + case tree.TenantStopService: + newMode = descpb.TenantInfo_NONE + case tree.TenantStartServiceExternal: + newMode = descpb.TenantInfo_EXTERNAL + case tree.TenantStartServiceShared: + newMode = descpb.TenantInfo_SHARED + default: + return nil, errors.AssertionFailedf("unhandled case: %+v", n) + } + + tspec, err := p.planTenantSpec(ctx, n.TenantSpec, "ALTER TENANT SERVICE") + if err != nil { + return nil, err + } + return &alterTenantServiceNode{ + tenantSpec: tspec, + newMode: newMode, + }, nil +} + +func (n *alterTenantServiceNode) startExec(params runParams) error { + rec, err := n.tenantSpec.getTenantInfo(params.ctx, params.p) + if err != nil { + return err + } + return params.p.setTenantService(params.ctx, rec, n.newMode) +} + +func (n *alterTenantServiceNode) Next(_ runParams) (bool, error) { return false, nil } +func (n *alterTenantServiceNode) Values() tree.Datums { return tree.Datums{} } +func (n *alterTenantServiceNode) Close(_ context.Context) {} diff --git a/pkg/sql/tenant_test.go b/pkg/sql/tenant_test.go index 3da8947352e1..659a75cb879f 100644 --- a/pkg/sql/tenant_test.go +++ b/pkg/sql/tenant_test.go @@ -88,6 +88,7 @@ SELECT id, active FROM system.tenants WHERE id = 10 checkKVsExistForTenant(t, true /* shouldExist */) // Destroy the tenant, make sure it does not have data and state. + tdb.Exec(t, "ALTER TENANT [10] STOP SERVICE") tdb.Exec(t, "DROP TENANT [10] IMMEDIATE") tdb.CheckQueryResults(t, tenantStateQuery, [][]string{}) checkKVsExistForTenant(t, false /* shouldExist */) diff --git a/pkg/sql/tenant_update.go b/pkg/sql/tenant_update.go index 1efbacf7c921..9f5922507460 100644 --- a/pkg/sql/tenant_update.go +++ b/pkg/sql/tenant_update.go @@ -50,7 +50,10 @@ func UpdateTenantRecord( `UPDATE system.tenants SET active = $2, info = $3 WHERE id = $1`, tenID, active, infoBytes, ); err != nil { - return errors.Wrap(err, "activating tenant") + if pgerror.GetPGCode(err) == pgcode.UniqueViolation { + return pgerror.Newf(pgcode.DuplicateObject, "name %q is already taken", info.Name) + } + return err } else if num != 1 { logcrash.ReportOrPanic(ctx, &settings.SV, "unexpected number of rows affected: %d", num) } @@ -64,6 +67,10 @@ func validateTenantInfo(info *descpb.TenantInfo) error { if info.DroppedName != "" && info.DataState != descpb.TenantInfo_DROP { return errors.Newf("tenant in data state %v with dropped name %q", info.DataState, info.DroppedName) } + if info.ServiceMode != descpb.TenantInfo_NONE && info.DataState != descpb.TenantInfo_READY { + return errors.Newf("cannot use tenant service mode %v with data state %v", + info.ServiceMode, info.DataState) + } return nil } @@ -108,7 +115,12 @@ func (p *planner) UpdateTenantResourceLimits( // The caller is responsible for checking that the user is authorized // to take this action. func ActivateTenant( - ctx context.Context, settings *cluster.Settings, codec keys.SQLCodec, txn isql.Txn, tenID uint64, + ctx context.Context, + settings *cluster.Settings, + codec keys.SQLCodec, + txn isql.Txn, + tenID uint64, + serviceMode descpb.TenantInfo_ServiceMode, ) error { const op = "activate" if err := rejectIfCantCoordinateMultiTenancy(codec, op); err != nil { @@ -126,6 +138,7 @@ func ActivateTenant( // Mark the tenant as active. info.DataState = descpb.TenantInfo_READY + info.ServiceMode = serviceMode if err := UpdateTenantRecord(ctx, settings, txn, info); err != nil { return errors.Wrap(err, "activating tenant") } @@ -133,8 +146,41 @@ func ActivateTenant( return nil } +func (p *planner) setTenantService( + ctx context.Context, info *descpb.TenantInfo, newMode descpb.TenantInfo_ServiceMode, +) error { + if p.EvalContext().TxnReadOnly { + return readOnlyError("ALTER TENANT SERVICE") + } + + if err := p.RequireAdminRole(ctx, "set tenant service"); err != nil { + return err + } + if err := rejectIfCantCoordinateMultiTenancy(p.ExecCfg().Codec, "set tenant service"); err != nil { + return err + } + if err := rejectIfSystemTenant(info.ID, "set tenant service"); err != nil { + return err + } + + if newMode == info.ServiceMode { + // No-op. Do nothing. + return nil + } + + if newMode != descpb.TenantInfo_NONE && info.ServiceMode != descpb.TenantInfo_NONE { + return errors.WithHint(pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "cannot change service mode %v to %v directly", + info.ServiceMode, newMode), + "Use ALTER TENANT STOP SERVICE first.") + } + + info.ServiceMode = newMode + return UpdateTenantRecord(ctx, p.ExecCfg().Settings, p.InternalSQLTxn(), info) +} + func (p *planner) renameTenant( - ctx context.Context, tenantID uint64, tenantName roachpb.TenantName, + ctx context.Context, info *descpb.TenantInfo, newName roachpb.TenantName, ) error { if p.EvalContext().TxnReadOnly { return readOnlyError("ALTER TENANT RENAME TO") @@ -146,12 +192,12 @@ func (p *planner) renameTenant( if err := rejectIfCantCoordinateMultiTenancy(p.ExecCfg().Codec, "rename tenant"); err != nil { return err } - if err := rejectIfSystemTenant(tenantID, "rename"); err != nil { + if err := rejectIfSystemTenant(info.ID, "rename"); err != nil { return err } - if tenantName != "" { - if err := tenantName.IsValid(); err != nil { + if newName != "" { + if err := newName.IsValid(); err != nil { return pgerror.WithCandidateCode(err, pgcode.Syntax) } @@ -160,21 +206,14 @@ func (p *planner) renameTenant( } } - if num, err := p.InternalSQLTxn().ExecEx( - ctx, "rename-tenant", p.txn, sessiondata.NodeUserSessionDataOverride, - `UPDATE system.public.tenants -SET info = -crdb_internal.json_to_pb('cockroach.sql.sqlbase.TenantInfo', - crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) || - json_build_object('name', $2)) -WHERE id = $1`, tenantID, tenantName); err != nil { - if pgerror.GetPGCode(err) == pgcode.UniqueViolation { - return pgerror.Newf(pgcode.DuplicateObject, "name %q is already taken", tenantName) - } - return errors.Wrap(err, "renaming tenant") - } else if num != 1 { - return pgerror.Newf(pgcode.UndefinedObject, "tenant %d not found", tenantID) + if info.ServiceMode != descpb.TenantInfo_NONE { + return errors.WithHint(pgerror.Newf(pgcode.ObjectNotInPrerequisiteState, + "cannot rename tenant in service mode %v", info.ServiceMode), + "Use ALTER TENANT STOP SERVICE before renaming a tenant.") } - return nil + info.Name = newName + return errors.Wrap( + UpdateTenantRecord(ctx, p.ExecCfg().Settings, p.InternalSQLTxn(), info), + "renaming tenant") } diff --git a/pkg/sql/walk.go b/pkg/sql/walk.go index acf57492ce24..5aa166399ac5 100644 --- a/pkg/sql/walk.go +++ b/pkg/sql/walk.go @@ -216,6 +216,7 @@ func (v *planVisitor) visitInternal(plan planNode, name string) { } case *alterTenantSetClusterSettingNode: + case *alterTenantServiceNode: case *createViewNode: case *setVarNode: case *setClusterSettingNode: @@ -361,6 +362,7 @@ var planNodeNames = map[reflect.Type]string{ reflect.TypeOf(&alterTableSetLocalityNode{}): "alter table set locality", reflect.TypeOf(&alterTableSetSchemaNode{}): "alter table set schema", reflect.TypeOf(&alterTenantSetClusterSettingNode{}): "alter tenant set cluster setting", + reflect.TypeOf(&alterTenantServiceNode{}): "alter tenant service", reflect.TypeOf(&alterTypeNode{}): "alter type", reflect.TypeOf(&alterRoleNode{}): "alter role", reflect.TypeOf(&alterRoleSetNode{}): "alter role set var",