From 41f5a0421e32f267340324fd0f0faa0c3acad3ad Mon Sep 17 00:00:00 2001 From: Raphael 'kena' Poss Date: Fri, 20 Jan 2023 14:49:37 +0100 Subject: [PATCH] sql: improve tenant records This commit contains the following changes: - rename "state" to "data_state", "active" to "ready" - deprecate the column "active" since it mirrors "data_state' - stored, non-virtual columns for "name", "data_state", "service_mode" Details follow. **rename TenantInfo.State to DataState, "ACTIVE" to "READY"** We've discovered that we'd like to separate the readiness of the data from the activation of the service. To emphasize this, this commit renames the field "State" to "DataState". Additionally, the state "ACTIVE" was confusing as it suggests that something is running, whereas it merely marks the tenant data as ready for use. So this commit also renames that state accordingly. **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. **make the output columns of SHOW TENANT lowercase** All the SHOW statements report status-like data in lowercase. SHOW TENANT(s) should not be different. **use actual SQL columns for the TenantInfo fields** Release note: None --- docs/generated/sql/bnf/stmt_block.bnf | 3 + pkg/ccl/backupccl/backup_planning_tenant.go | 67 +++++-- pkg/ccl/backupccl/backup_test.go | 164 +++++++++--------- pkg/ccl/backupccl/restore_job.go | 35 ++-- .../testdata/backup-restore/restore-tenants | 28 +-- .../testdata/logic_test/tenant_usage | 1 + .../tenant/directory_cache_test.go | 21 ++- .../streamingest/alter_replication_job.go | 4 +- .../replication_stream_e2e_test.go | 12 +- .../streamingest/stream_ingestion_job.go | 2 +- .../streamingest/stream_ingestion_planning.go | 13 +- .../streamingccl/streamingest/testdata/simple | 8 +- pkg/ccl/testccl/sqlccl/tenant_gc_test.go | 7 +- pkg/clusterversion/cockroach_versions.go | 6 +- pkg/sql/BUILD.bazel | 1 + pkg/sql/catalog/bootstrap/metadata.go | 14 +- pkg/sql/catalog/bootstrap/testdata/testdata | 7 +- pkg/sql/catalog/colinfo/result_columns.go | 3 +- pkg/sql/catalog/descpb/BUILD.bazel | 1 + pkg/sql/catalog/descpb/tenant.go | 104 +++++++++++ pkg/sql/catalog/descpb/tenant.proto | 57 ++++-- pkg/sql/catalog/systemschema/system.go | 41 +++-- pkg/sql/drop_tenant.go | 2 +- pkg/sql/faketreeeval/evalctx.go | 2 +- pkg/sql/gcjob/tenant_garbage_collection.go | 8 +- pkg/sql/gcjob_test/gc_job_test.go | 13 +- .../testdata/logic_test/crdb_internal_catalog | 2 +- .../testdata/logic_test/information_schema | 3 +- .../logictest/testdata/logic_test/pg_catalog | 2 + pkg/sql/logictest/testdata/logic_test/tenant | 102 +++++------ .../testdata/logic_test/tenant_builtins | 52 +++--- 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/planhook.go | 2 +- 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 | 30 ++-- pkg/sql/tenant_accessors.go | 74 +++++--- pkg/sql/tenant_creation.go | 56 ++++-- pkg/sql/tenant_deletion.go | 20 ++- pkg/sql/tenant_gc.go | 14 +- pkg/sql/tenant_service.go | 68 ++++++++ pkg/sql/tenant_spec.go | 10 +- pkg/sql/tenant_test.go | 1 + pkg/sql/tenant_update.go | 136 +++++++++++---- pkg/sql/tests/system_table_test.go | 2 +- pkg/sql/tests/testdata/initial_keys | 3 +- pkg/sql/walk.go | 2 + .../upgrades/tenant_table_migration.go | 89 ++++++---- .../upgrades/tenant_table_migration_test.go | 7 +- pkg/upgrade/upgrades/upgrades.go | 6 +- 57 files changed, 1038 insertions(+), 428 deletions(-) create mode 100644 pkg/sql/catalog/descpb/tenant.go 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_planning_tenant.go b/pkg/ccl/backupccl/backup_planning_tenant.go index 68a1110314cd..ea3b31384926 100644 --- a/pkg/ccl/backupccl/backup_planning_tenant.go +++ b/pkg/ccl/backupccl/backup_planning_tenant.go @@ -22,12 +22,14 @@ import ( const tenantMetadataQuery = ` SELECT tenants.id, /* 0 */ - tenants.active, /* 1 */ - tenants.info, /* 2 */ - tenant_usage.ru_burst_limit, /* 3 */ - tenant_usage.ru_refill_rate, /* 4 */ - tenant_usage.ru_current, /* 5 */ - tenant_usage.total_consumption /* 6 */ + tenants.info, /* 1 */ + tenants.name, /* 2 */ + tenants.data_state, /* 3 */ + tenants.service_mode, /* 4 */ + tenant_usage.ru_burst_limit, /* 5 */ + tenant_usage.ru_refill_rate, /* 6 */ + tenant_usage.ru_current, /* 7 */ + tenant_usage.total_consumption /* 8 */ FROM system.tenants LEFT JOIN system.tenant_usage ON @@ -35,7 +37,7 @@ FROM ` func tenantMetadataFromRow(row tree.Datums) (descpb.TenantInfoWithUsage, error) { - if len(row) != 7 { + if len(row) != 9 { return descpb.TenantInfoWithUsage{}, errors.AssertionFailedf( "unexpected row size %d from tenant metadata query", len(row), ) @@ -44,30 +46,58 @@ func tenantMetadataFromRow(row tree.Datums) (descpb.TenantInfoWithUsage, error) id := uint64(tree.MustBeDInt(row[0])) res := descpb.TenantInfoWithUsage{ TenantInfo: descpb.TenantInfo{ + // for compatibility + DeprecatedID: id, + }, + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ ID: id, }, } - infoBytes := []byte(tree.MustBeDBytes(row[2])) + infoBytes := []byte(tree.MustBeDBytes(row[1])) if err := protoutil.Unmarshal(infoBytes, &res.TenantInfo); err != nil { return descpb.TenantInfoWithUsage{}, err } + if row[2] != tree.DNull { + res.Name = roachpb.TenantName(tree.MustBeDString(row[2])) + } + if row[3] != tree.DNull { + res.DataState = descpb.TenantDataState(tree.MustBeDInt(row[3])) + } else { + // Pre-v23.1 info struct. + switch res.TenantInfo.DeprecatedDataState { + case descpb.TenantInfo_READY: + res.DataState = descpb.DataStateReady + case descpb.TenantInfo_ADD: + res.DataState = descpb.DataStateAdd + case descpb.TenantInfo_DROP: + res.DataState = descpb.DataStateDrop + return res, errors.AssertionFailedf("unhandled: %d", res.TenantInfo.DeprecatedDataState) + } + } + res.ServiceMode = descpb.ServiceModeNone + if row[4] != tree.DNull { + res.ServiceMode = descpb.TenantServiceMode(tree.MustBeDInt(row[4])) + } else if res.DataState == descpb.DataStateReady { + // Records created for CC Serverless pre-v23.1. + res.ServiceMode = descpb.ServiceModeExternal + } // If this tenant had no reported consumption and its token bucket was not // configured, the tenant_usage values are all NULL. // // It should be sufficient to check any one value, but we check all of them // just to be defensive (in case the table contains invalid data). - for _, d := range row[3:5] { + for _, d := range row[5:] { if d == tree.DNull { return res, nil } } res.Usage = &descpb.TenantInfoWithUsage_Usage{ - RUBurstLimit: float64(tree.MustBeDFloat(row[3])), - RURefillRate: float64(tree.MustBeDFloat(row[4])), - RUCurrent: float64(tree.MustBeDFloat(row[5])), + RUBurstLimit: float64(tree.MustBeDFloat(row[5])), + RURefillRate: float64(tree.MustBeDFloat(row[6])), + RUCurrent: float64(tree.MustBeDFloat(row[7])), } - if row[6] != tree.DNull { - consumptionBytes := []byte(tree.MustBeDBytes(row[6])) + if row[8] != tree.DNull { + consumptionBytes := []byte(tree.MustBeDBytes(row[8])) if err := protoutil.Unmarshal(consumptionBytes, &res.Usage.Consumption); err != nil { return descpb.TenantInfoWithUsage{}, err } @@ -88,11 +118,14 @@ func retrieveSingleTenantMetadata( if row == nil { return descpb.TenantInfoWithUsage{}, errors.Errorf("tenant %s does not exist", tenantID) } - if !tree.MustBeDBool(row[1]) { + info, err := tenantMetadataFromRow(row) + if err != nil { + return descpb.TenantInfoWithUsage{}, err + } + if info.DataState != descpb.DataStateReady { return descpb.TenantInfoWithUsage{}, errors.Errorf("tenant %s is not active", tenantID) } - - return tenantMetadataFromRow(row) + return info, nil } func retrieveAllTenantsMetadata( diff --git a/pkg/ccl/backupccl/backup_test.go b/pkg/ccl/backupccl/backup_test.go index 33195366346a..2faedf20642b 100644 --- a/pkg/ccl/backupccl/backup_test.go +++ b/pkg/ccl/backupccl/backup_test.go @@ -6881,28 +6881,29 @@ func TestBackupRestoreTenant(t *testing.T) { defer restoreTC.Stopper().Stop(ctx) restoreDB := sqlutils.MakeSQLRunner(restoreTC.Conns[0]) - restoreDB.CheckQueryResults(t, `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, [][]string{ - {`1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + restoreDB.CheckQueryResults(t, `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ + { + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) restoreDB.CheckQueryResults(t, - `SELECT id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants`, + `SELECT id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) FROM system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `true`, - `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-10", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `10`, `true`, `tenant-10`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "10"}`, }, }, ) @@ -6930,19 +6931,19 @@ func TestBackupRestoreTenant(t *testing.T) { // Mark tenant as DROP. restoreDB.Exec(t, `DROP TENANT [10]`) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `false`, - `NULL`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "tenant-10", "id": "10", "name": "", "state": "DROP", "tenantReplicationJobId": "0"}`, + `10`, `false`, `NULL`, + strconv.Itoa(int(descpb.DataStateDrop)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedDataState": "DROP", "deprecatedId": "10", "droppedName": "tenant-10"}`, }, }, ) @@ -6965,19 +6966,19 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `true`, - `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-10", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `10`, `true`, `tenant-10`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "10"}`, }, }, ) @@ -7000,30 +7001,30 @@ func TestBackupRestoreTenant(t *testing.T) { ) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/t10'`) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `true`, - `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-10", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `10`, `true`, `tenant-10`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "10"}`, }, }, ) @@ -7044,30 +7045,30 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB := sqlutils.MakeSQLRunner(restoreTC.Conns[0]) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, }) restoreDB.Exec(t, `RESTORE TENANT 10 FROM 'nodelocal://1/clusterwide'`) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `true`, - `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-10", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `10`, `true`, `tenant-10`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "10"}`, }, }, ) @@ -7099,41 +7100,42 @@ func TestBackupRestoreTenant(t *testing.T) { restoreDB := sqlutils.MakeSQLRunner(restoreTC.Conns[0]) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, - `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, }) restoreDB.Exec(t, `RESTORE FROM 'nodelocal://1/clusterwide'`) restoreDB.CheckQueryResults(t, - `select id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) from system.tenants`, + `select id, active, name, data_state, service_mode, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info) from system.tenants`, [][]string{ { - `1`, - `true`, `system`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `1`, `true`, `system`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeShared)), + `{"capabilities": {}, "deprecatedId": "1"}`, }, { - `10`, - `true`, - `tenant-10`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-10", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `10`, `true`, `tenant-10`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "10"}`, }, { - `11`, - `true`, - `tenant-11`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "11", "name": "tenant-11", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `11`, `true`, `tenant-11`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "11"}`, }, { - `20`, - `true`, - `tenant-20`, - `{"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "20", "name": "tenant-20", "state": "ACTIVE", "tenantReplicationJobId": "0"}`, + `20`, `true`, `tenant-20`, + strconv.Itoa(int(descpb.DataStateReady)), + strconv.Itoa(int(descpb.ServiceModeNone)), + `{"capabilities": {}, "deprecatedId": "20"}`, }, }, ) diff --git a/pkg/ccl/backupccl/restore_job.go b/pkg/ccl/backupccl/restore_job.go index 863b6d193365..2da56b26e8bb 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.State { - case descpb.TenantInfo_ACTIVE: - // If the tenant was backed up in an `ACTIVE` state then we create - // the restored record in an `ADDING` state and mark it `ACTIVE` at + for _, tenantInfoCopy := range details.Tenants { + switch tenantInfoCopy.DataState { + case descpb.DataStateReady: + // 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.State = descpb.TenantInfo_ADD - case descpb.TenantInfo_DROP, descpb.TenantInfo_ADD: + tenantInfoCopy.ServiceMode = descpb.ServiceModeNone + tenantInfoCopy.DataState = descpb.DataStateAdd + case descpb.DataStateDrop, descpb.DataStateAdd: // 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 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 @@ -2191,21 +2192,21 @@ func (r *restoreResumer) publishDescriptors( } for _, tenant := range details.Tenants { - switch tenant.State { - case descpb.TenantInfo_ACTIVE: - // If the tenant was backed up in an `ACTIVE` state then we must activate + switch tenant.DataState { + case descpb.DataStateReady: + // If the tenant was backed up in the `READY` state then we must activate // 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 } - case descpb.TenantInfo_DROP, descpb.TenantInfo_ADD: + case descpb.DataStateDrop, descpb.DataStateAdd: // If the tenant was backed up in a `DROP` or `ADD` state then we do not // want to activate the tenant. default: - return errors.AssertionFailedf("unknown tenant state %v", tenant) + return errors.AssertionFailedf("unknown tenant data state %v", tenant) } } @@ -2325,10 +2326,10 @@ func (r *restoreResumer) OnFailOrCancel( ctx context.Context, txn isql.Txn, ) error { for _, tenant := range details.Tenants { - tenant.State = descpb.TenantInfo_DROP + tenant.DataState = descpb.DataStateDrop // This is already a job so no need to spin up a gc job for the tenant; // instead just GC the data eagerly. - if err := sql.GCTenantSync(ctx, execCfg, &tenant.TenantInfo); err != nil { + if err := sql.GCTenantSync(ctx, execCfg, tenant.ToExtended()); 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 a302c03775fa..d1f30fa569e2 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; +SELECT id,name,data_state,service_mode,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "tenant-5", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "tenant-6", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 system 0 2 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +5 2 1 false {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "5", "droppedName": "tenant-5", "tenantReplicationJobId": "0"} +6 tenant-6 0 0 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "6", "droppedName": "", "tenantReplicationJobId": "0"} exec-sql BACKUP INTO 'nodelocal://1/cluster' @@ -47,11 +47,11 @@ RESTORE FROM LATEST IN 'nodelocal://1/cluster' # A dropped tenant should be restored as an inactive tenant. query-sql -SELECT id,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; +SELECT id,name,data_state,service_mode,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "tenant-6", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 system 0 2 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +5 tenant-5 2 1 false {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "5", "droppedName": "tenant-5", "tenantReplicationJobId": "0"} +6 tenant-6 0 0 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "6", "droppedName": "", "tenantReplicationJobId": "0"} exec-sql expect-error-regex=(tenant 6 already exists) RESTORE TENANT 6 FROM LATEST IN 'nodelocal://1/tenant6'; @@ -73,9 +73,9 @@ 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; +SELECT id,name,data_state,service_mode,active,crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants; ---- -1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "newname", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false {"capabilities": {"canAdminSplit": false}, "droppedName": "tenant-5", "id": "5", "name": "tenant-5", "state": "DROP", "tenantReplicationJobId": "0"} -6 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "6", "name": "tenant-6", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 system 0 2 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 newname 0 0 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +5 tenant-5 2 1 false {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "5", "droppedName": "tenant-5", "tenantReplicationJobId": "0"} +6 tenant-6 0 0 true {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "6", "droppedName": "", "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/alter_replication_job.go b/pkg/ccl/streamingccl/streamingest/alter_replication_job.go index 9985877d62dd..b60fdf30c2ac 100644 --- a/pkg/ccl/streamingccl/streamingest/alter_replication_job.go +++ b/pkg/ccl/streamingccl/streamingest/alter_replication_job.go @@ -201,7 +201,7 @@ func alterTenantJobCutover( jobRegistry *jobs.Registry, ptp protectedts.Storage, alterTenantStmt *tree.AlterTenantReplication, - tenInfo *descpb.TenantInfo, + tenInfo *descpb.ExtendedTenantInfo, cutoverTime hlc.Timestamp, ) error { if alterTenantStmt == nil || alterTenantStmt.Cutover == nil { @@ -262,7 +262,7 @@ func alterTenantOptions( txn isql.Txn, jobRegistry *jobs.Registry, options *resolvedTenantReplicationOptions, - tenInfo *descpb.TenantInfo, + tenInfo *descpb.ExtendedTenantInfo, ) error { return jobRegistry.UpdateJobWithTxn(ctx, tenInfo.TenantReplicationJobID, txn, false, /* useReadLock */ func(txn isql.Txn, md jobs.JobMetadata, ju *jobs.JobUpdater) error { diff --git a/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go b/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go index 96643875d496..94b744aa1244 100644 --- a/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go +++ b/pkg/ccl/streamingccl/streamingest/replication_stream_e2e_test.go @@ -428,7 +428,7 @@ func TestTenantStreamingDropTenantCancelsStream(t *testing.T) { } // Set GC TTL low, so that the GC job completes quickly in the test. - c.DestSysSQL.Exec(t, "ALTER RANGE tenants CONFIGURE ZONE USING gc.Ttlseconds = 1;") + c.DestSysSQL.Exec(t, "ALTER RANGE tenants CONFIGURE ZONE USING gc.ttlseconds = 1;") c.DestSysSQL.Exec(t, fmt.Sprintf("DROP TENANT %s", c.Args.DestTenantName)) jobutils.WaitForJobToCancel(c.T, c.DestSysSQL, jobspb.JobID(ingestionJobID)) jobutils.WaitForJobToCancel(c.T, c.SrcSysSQL, jobspb.JobID(producerJobID)) @@ -854,7 +854,7 @@ func TestTenantReplicationProtectedTimestampManagement(t *testing.T) { } // Set GC TTL low, so that the GC job completes quickly in the test. - c.DestSysSQL.Exec(t, "ALTER RANGE tenants CONFIGURE ZONE USING gc.Ttlseconds = 1;") + c.DestSysSQL.Exec(t, "ALTER RANGE tenants CONFIGURE ZONE USING gc.ttlseconds = 1;") c.DestSysSQL.Exec(t, fmt.Sprintf("DROP TENANT %s", c.Args.DestTenantName)) if !completeReplication { @@ -909,7 +909,7 @@ func TestTenantStreamingShowTenant(t *testing.T) { require.Equal(t, "destination", rowStr[0][1]) if rowStr[0][3] == "NULL" { // There is no source yet, therefore the replication is not fully initialized. - require.Equal(t, "INITIALIZING REPLICATION", rowStr[0][2]) + require.Equal(t, "initializing replication", rowStr[0][2]) } jobutils.WaitForJobToRun(c.T, c.SrcSysSQL, jobspb.JobID(producerJobID)) @@ -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, "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_job.go b/pkg/ccl/streamingccl/streamingest/stream_ingestion_job.go index cffcf3403c0e..fe420ee294eb 100644 --- a/pkg/ccl/streamingccl/streamingest/stream_ingestion_job.go +++ b/pkg/ccl/streamingccl/streamingest/stream_ingestion_job.go @@ -553,7 +553,7 @@ func activateTenant(ctx context.Context, execCtx interface{}, newTenantID roachp return err } - info.State = descpb.TenantInfo_ACTIVE + info.DataState = descpb.DataStateReady info.TenantReplicationJobID = 0 return sql.UpdateTenantRecord(ctx, p.ExecCfg().Settings, txn, info) }) diff --git a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go index 9c7debecc3ab..475873ac03d9 100644 --- a/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go +++ b/pkg/ccl/streamingccl/streamingest/stream_ingestion_planning.go @@ -149,16 +149,19 @@ 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, - State: descpb.TenantInfo_ADD, - Name: roachpb.TenantName(dstTenantName), TenantReplicationJobID: jobID, }, + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ + // dstTenantID may be zero which will cause auto-allocation. + ID: dstTenantID, + DataState: descpb.DataStateAdd, + ServiceMode: descpb.ServiceModeNone, + Name: roachpb.TenantName(dstTenantName), + }, } initialTenantZoneConfig, err := sql.GetHydratedZoneConfigForTenantsRange(ctx, p.Txn(), p.ExtendedEvalContext().Descs) diff --git a/pkg/ccl/streamingccl/streamingest/testdata/simple b/pkg/ccl/streamingccl/streamingest/testdata/simple index e893017ab55d..8fe1762d4d30 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 ACTIVE -10 source ACTIVE +1 system ready shared +10 source ready none query-sql as=destination-system SHOW TENANTS ---- -1 system ACTIVE -2 destination REPLICATING +1 system ready shared +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 210b32896bff..0baac7554b5b 100644 --- a/pkg/ccl/testccl/sqlccl/tenant_gc_test.go +++ b/pkg/ccl/testccl/sqlccl/tenant_gc_test.go @@ -121,7 +121,12 @@ func TestGCTenantRemovesSpanConfigs(t *testing.T) { ) error { return sql.TestingUpdateTenantRecord( ctx, ts.ClusterSettings(), txn, - &descpb.TenantInfo{ID: tenantID.ToUint64(), State: descpb.TenantInfo_DROP}, + &descpb.ExtendedTenantInfo{ + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ + ID: tenantID.ToUint64(), + ServiceMode: descpb.ServiceModeNone, + DataState: descpb.DataStateDrop, + }}, ) })) diff --git a/pkg/clusterversion/cockroach_versions.go b/pkg/clusterversion/cockroach_versions.go index 92bb5cc6d747..9cb3fcc120f5 100644 --- a/pkg/clusterversion/cockroach_versions.go +++ b/pkg/clusterversion/cockroach_versions.go @@ -327,8 +327,8 @@ const ( // the process of upgrading from 22.2 to 23.1. V23_1Start - // V23_1TenantNames adds a name column to system.tenants. - V23_1TenantNames + // V23_1TenantNamesStateAndServiceMode adds columns to system.tenants. + V23_1TenantNamesStateAndServiceMode // V23_1DescIDSequenceForSystemTenant migrates the descriptor ID generator // counter from a meta key to the system.descriptor_id_seq sequence for the @@ -632,7 +632,7 @@ var rawVersionsSingleton = keyedVersions{ Version: roachpb.Version{Major: 22, Minor: 2, Internal: 2}, }, { - Key: V23_1TenantNames, + Key: V23_1TenantNamesStateAndServiceMode, Version: roachpb.Version{Major: 22, Minor: 2, Internal: 4}, }, { 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/bootstrap/metadata.go b/pkg/sql/catalog/bootstrap/metadata.go index 64cfda8445f4..5462e5c20bff 100644 --- a/pkg/sql/catalog/bootstrap/metadata.go +++ b/pkg/sql/catalog/bootstrap/metadata.go @@ -561,9 +561,7 @@ func addSystemDatabaseToSchema( // system tenant entry. func addSystemTenantEntry(target *MetadataSchema) { info := descpb.TenantInfo{ - ID: roachpb.SystemTenantID.ToUint64(), - Name: catconstants.SystemTenantName, - State: descpb.TenantInfo_ACTIVE, + DeprecatedID: roachpb.SystemTenantID.ToUint64(), } infoBytes, err := protoutil.Marshal(&info) if err != nil { @@ -579,10 +577,18 @@ func addSystemTenantEntry(target *MetadataSchema) { } tenantsTableWriter := MakeKVWriter(target.codec, desc.(catalog.TableDescriptor)) kvs, err := tenantsTableWriter.RecordToKeyValues( + // ID tree.NewDInt(tree.DInt(roachpb.SystemTenantID.ToUint64())), + // active -- deprecated. tree.MakeDBool(true), + // info. tree.NewDBytes(tree.DBytes(infoBytes)), - tree.NewDString(string(info.Name)), + // name. + tree.NewDString(catconstants.SystemTenantName), + // data_state. + tree.NewDInt(tree.DInt(descpb.DataStateReady)), + // service_mode. + tree.NewDInt(tree.DInt(descpb.ServiceModeShared)), ) if err != nil { panic(err) diff --git a/pkg/sql/catalog/bootstrap/testdata/testdata b/pkg/sql/catalog/bootstrap/testdata/testdata index 310380cd1e3c..5edeaa17cd32 100644 --- a/pkg/sql/catalog/bootstrap/testdata/testdata +++ b/pkg/sql/catalog/bootstrap/testdata/testdata @@ -1,4 +1,4 @@ -system hash=0520333938dc6f1d408132c88fff8e23fadbb41a96c6cf10e6143790b53a80db +system hash=4ee50b7c0f19ab5c9bffdc9271cfda73deec1f1b96e021a8cd7177d56c33bda0 ---- [{"key":"04646573632d696467656e","value":"01c801"} ,{"key":"8b"} @@ -8,7 +8,7 @@ system hash=0520333938dc6f1d408132c88fff8e23fadbb41a96c6cf10e6143790b53a80db ,{"key":"8b898d8a89","value":"030aea020a057a6f6e65731805200128013a0042270a02696410011a0c08011040180030005014600020003000680070007800800100880100980100422b0a06636f6e66696710021a0c08081000180030005011600020013000680070007800800100880100980100480352630a077072696d61727910011801220269642a06636f6e666967300140004a10080010001a00200028003000380040005a0070027a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060026a250a0d0a0561646d696e10e00318e0030a0c0a04726f6f7410e00318e00312046e6f64651802800101880103980100b201130a077072696d61727910001a02696420012800b2011c0a0c66616d5f325f636f6e66696710021a06636f6e66696720022802b80103c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880302a80300b00300"} ,{"key":"8b898e8a89","value":"030aa9040a0873657474696e67731806200128013a0042290a046e616d6510011a0c08071000180030005019600020003000680070007800800100880100980100422a0a0576616c756510021a0c0807100018003000501960002000300068007000780080010088010098010042440a0b6c6173745570646174656410031a0d080510001800300050da08600020002a116e6f7728293a3a3a54494d455354414d503000680070007800800100880100980100422e0a0976616c75655479706510041a0c0807100018003000501960002001300068007000780080010088010098010048055280010a077072696d6172791001180122046e616d652a0576616c75652a0b6c617374557064617465642a0976616c756554797065300140004a10080010001a00200028003000380040005a007002700370047a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060026a250a0d0a0561646d696e10e00318e0030a0c0a04726f6f7410e00318e00312046e6f64651802800101880103980100b201590a2666616d5f305f6e616d655f76616c75655f6c617374557064617465645f76616c75655479706510001a046e616d651a0576616c75651a0b6c617374557064617465641a0976616c75655479706520012002200320042800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880302a80300b00300"} ,{"key":"8b898f8a89","value":"030ac4020a1164657363726970746f725f69645f7365711807200128013a00422a0a0576616c756510011a0c080110401800300050146000200030006800700078008001008801009801004800525c0a077072696d61727910011800220576616c7565300140004a10080010001a00200028003000380040005a007a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00100e0010060006a210a0b0a0561646d696e102018200a0a0a04726f6f741020182012046e6f64651802800100880103980100b201160a077072696d61727910001a0576616c756520012801b80100c20100e2011c0801100118ffffffffffffffff7f2001280032040800100038014200e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880300a80300b00300"} -,{"key":"8b89908a89","value":"030a87050a0774656e616e74731808200128013a0042270a02696410011a0c0801104018003000501460002000300068007000780080010088010098010042310a0661637469766510021a0c08001000180030005010600020002a0474727565300068007000780080010088010098010042290a04696e666f10031a0c080810001800300050116000200130006800700078008001008801009801004288010a046e616d6510041a0c080710001800300050196000200130005a5d637264625f696e7465726e616c2e70625f746f5f6a736f6e2827636f636b726f6163682e73716c2e73716c626173652e54656e616e74496e666f273a3a3a535452494e472c20696e666f292d3e3e276e616d65273a3a3a535452494e476800700078008001018801009801004805526b0a077072696d61727910011801220269642a066163746976652a04696e666f300140004a10080010001a00200028003000380040005a00700270037a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00102e001005a660a1074656e616e74735f6e616d655f6964781002180122046e616d653004380140004a10080010001a00200028003000380040005a007a0408002000800100880100900103980100a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060036a210a0b0a0561646d696e102018200a0a0a04726f6f741020182012046e6f64651802800101880103980100b201250a077072696d61727910001a0269641a066163746976651a04696e666f2001200220032800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880303a80300b00300"} +,{"key":"8b89908a89","value":"030ad0060a0774656e616e74731808200128013a0042270a02696410011a0c0801104018003000501460002000300068007000780080010088010098010042310a0661637469766510021a0c08001000180030005010600020002a0474727565300068007000780080010088010098010042290a04696e666f10031a0c0808100018003000501160002001300068007000780080010088010098010042290a046e616d6510041a0c08071000180030005019600020013000680070007800800100880100980100422f0a0a646174615f737461746510051a0c0801104018003000501460002001300068007000780080010088010098010042310a0c736572766963655f6d6f646510061a0c0801104018003000501460002001300068007000780080010088010098010048075291010a077072696d61727910011801220269642a066163746976652a04696e666f2a046e616d652a0a646174615f73746174652a0c736572766963655f6d6f6465300140004a10080010001a00200028003000380040005a00700270037004700570067a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00102e001005a660a1074656e616e74735f6e616d655f6964781002180122046e616d653004380140004a10080010001a00200028003000380040005a007a0408002000800100880100900103980100a20106080012001800a80100b20100ba0100c00100c80100d00101e001005a760a1874656e616e74735f736572766963655f6d6f64655f69647810031800220c736572766963655f6d6f64653006380140004a10080010001a00200028003000380040005a007a0408002000800100880100900103980100a20106080012001800a80100b20100ba0100c00100c80100d00100e0010060046a210a0b0a0561646d696e102018200a0a0a04726f6f741020182012046e6f64651802800101880103980100b2014b0a077072696d61727910001a0269641a066163746976651a04696e666f1a046e616d651a0a646174615f73746174651a0c736572766963655f6d6f64652001200220032004200520062800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880303a80300b00300"} ,{"key":"8b89938a89","value":"030afa030a056c65617365180b200128013a00422b0a0664657363494410011a0c08011040180030005014600020003000680070007800800100880100980100422c0a0776657273696f6e10021a0c08011040180030005014600020003000680070007800800100880100980100422b0a066e6f6465494410031a0c0801104018003000501460002000300068007000780080010088010098010042300a0a65787069726174696f6e10041a0d080510001800300050da0860002000300068007000780080010088010098010048055286010a077072696d617279100118012206646573634944220776657273696f6e220a65787069726174696f6e22066e6f64654944300130023004300340004000400040004a10080010001a00200028003000380040005a007a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060026a250a0d0a0561646d696e10e00318e0030a0c0a04726f6f7410e00318e00312046e6f64651802800101880103980100b2013a0a077072696d61727910001a066465736349441a0776657273696f6e1a066e6f646549441a0a65787069726174696f6e20012002200320042800b80101c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880302a80300b00300"} ,{"key":"8b89948a89","value":"030afa050a086576656e746c6f67180c200128013a00422f0a0974696d657374616d7010011a0d080510001800300050da08600020003000680070007800800100880100980100422e0a096576656e745479706510021a0c08071000180030005019600020003000680070007800800100880100980100422d0a08746172676574494410031a0c0801104018003000501460002000300068007000780080010088010098010042300a0b7265706f7274696e67494410041a0c0801104018003000501460002000300068007000780080010088010098010042290a04696e666f10051a0c0807100018003000501960002001300068007000780080010088010098010042380a08756e69717565494410061a0c08081000180030005011600020002a09757569645f7634282930006800700078008001008801009801004807529e010a077072696d61727910011801220974696d657374616d702208756e6971756549442a096576656e74547970652a0874617267657449442a0b7265706f7274696e6749442a04696e666f30013006400040004a10080010001a00200028003000380040005a0070027003700470057a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060026a250a0d0a0561646d696e10e00318e0030a0c0a04726f6f7410e00318e00312046e6f64651802800101880103980100b201260a077072696d61727910001a0974696d657374616d701a08756e697175654944200120062800b201220a0f66616d5f325f6576656e745479706510021a096576656e745479706520022802b201200a0e66616d5f335f746172676574494410031a08746172676574494420032803b201260a1166616d5f345f7265706f7274696e67494410041a0b7265706f7274696e67494420042804b201180a0a66616d5f355f696e666f10051a04696e666f20052805b80106c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880302a80300b00300"} ,{"key":"8b89958a89","value":"030ad9060a0872616e67656c6f67180d200128013a00422f0a0974696d657374616d7010011a0d080510001800300050da08600020003000680070007800800100880100980100422c0a0772616e6765494410021a0c08011040180030005014600020003000680070007800800100880100980100422c0a0773746f7265494410031a0c08011040180030005014600020003000680070007800800100880100980100422e0a096576656e745479706510041a0c0807100018003000501960002000300068007000780080010088010098010042310a0c6f7468657252616e6765494410051a0c0801104018003000501460002001300068007000780080010088010098010042290a04696e666f10061a0c08071000180030005019600020013000680070007800800100880100980100423d0a08756e69717565494410071a0c08011040180030005014600020002a0e756e697175655f726f77696428293000680070007800800100880100980100480852a9010a077072696d61727910011801220974696d657374616d702208756e6971756549442a0772616e676549442a0773746f726549442a096576656e74547970652a0c6f7468657252616e676549442a04696e666f30013007400040004a10080010001a00200028003000380040005a00700270037004700570067a0408002000800100880100900104980101a20106080012001800a80100b20100ba0100c00100c80100d00101e0010060026a250a0d0a0561646d696e10e00318e0030a0c0a04726f6f7410e00318e00312046e6f64651802800101880103980100b201260a077072696d61727910001a0974696d657374616d701a08756e697175654944200120072800b2011e0a0d66616d5f325f72616e6765494410021a0772616e6765494420022802b2011e0a0d66616d5f335f73746f7265494410031a0773746f7265494420032803b201220a0f66616d5f345f6576656e745479706510041a096576656e745479706520042804b201280a1266616d5f355f6f7468657252616e6765494410051a0c6f7468657252616e6765494420052805b201180a0a66616d5f365f696e666f10061a04696e666f20062806b80107c20100e80100f2010408001200f801008002009202009a0200b20200b80200c0021dc80200e00200800300880302a80300b00300"} @@ -59,8 +59,9 @@ system hash=0520333938dc6f1d408132c88fff8e23fadbb41a96c6cf10e6143790b53a80db ,{"key":"8f"} ,{"key":"8f898888","value":"01c801"} ,{"key":"90"} -,{"key":"90898988","value":"0a2a1612080110001a0673797374656d220028003200"} +,{"key":"90898988","value":"0a2a160a08011000220028003200160673797374656d13001304"} ,{"key":"908a1273797374656d000188","value":"0389"} +,{"key":"908b8a8988","value":"03"} ,{"key":"93"} ,{"key":"94"} ,{"key":"95"} diff --git a/pkg/sql/catalog/colinfo/result_columns.go b/pkg/sql/catalog/colinfo/result_columns.go index 3f75744c281f..b99baf8bfe51 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_state", Typ: types.String}, + {Name: "service_mode", Typ: types.String}, } // TenantColumnsWithReplication is appended to TenantColumns for diff --git a/pkg/sql/catalog/descpb/BUILD.bazel b/pkg/sql/catalog/descpb/BUILD.bazel index 46735b7fac31..0ed2b810488d 100644 --- a/pkg/sql/catalog/descpb/BUILD.bazel +++ b/pkg/sql/catalog/descpb/BUILD.bazel @@ -14,6 +14,7 @@ go_library( "join_type.go", "locking.go", "structured.go", + "tenant.go", ":gen-formatversion-stringer", # keep ], embed = [":descpb_go_proto"], diff --git a/pkg/sql/catalog/descpb/tenant.go b/pkg/sql/catalog/descpb/tenant.go new file mode 100644 index 000000000000..efb86bdfc1eb --- /dev/null +++ b/pkg/sql/catalog/descpb/tenant.go @@ -0,0 +1,104 @@ +// 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 descpb + +import "fmt" + +// TenantServiceMode describes how tenants can be served to clients. +type TenantServiceMode uint8 + +// Note: the constant values below are stored in system.tenants.service_mode. +const ( + // ServiceModeExternal says that service is allowed using + // separate processes. + // This is the default value for backward-compatibility with + // records created for CockroachCloud Serverless pre-v23.1. + ServiceModeExternal TenantServiceMode = 0 + // ServiceModeNode says that no service is allowed. + ServiceModeNone TenantServiceMode = 1 + // ServiceModeShared says that service is allowed using shared-process + // multitenancy on KV nodes. + // This mode causes KV nodes to spontaneously start the SQL service + // for the tenant. + ServiceModeShared TenantServiceMode = 2 +) + +// String implements fmt.Stringer. +func (s TenantServiceMode) String() string { + switch s { + case ServiceModeExternal: + return "external" + case ServiceModeNone: + return "none" + case ServiceModeShared: + return "shared" + default: + return fmt.Sprintf("unimplemented-%d", int(s)) + } +} + +// TenantServiceModeValues facilitates the string -> TenantServiceMode conversion. +var TenantServiceModeValues = map[string]TenantServiceMode{ + "external": ServiceModeExternal, + "none": ServiceModeNone, + "shared": ServiceModeShared, +} + +// TenantDataState describes the state of a tenant's logical keyspace. +type TenantDataState uint8 + +// Note: the constant values below are stored in system.tenants.data_state. +const ( + // DataStateReady indicates data is ready and SQL servers can access it. + DataStateReady TenantDataState = 0 + // DataStateAdd indicates tenant data is being added. Not available + // for SQL sessions. + DataStateAdd TenantDataState = 1 + // DataStateDrop indicates tenant data is being deleted. Not + // available for SQL sessions. + DataStateDrop TenantDataState = 2 +) + +// String implements fmt.Stringer. +func (s TenantDataState) String() string { + switch s { + case DataStateReady: + return "ready" + case DataStateAdd: + return "add" + case DataStateDrop: + return "drop" + default: + return fmt.Sprintf("unimplemented-%d", int(s)) + } +} + +// TenantDataStateValues facilitates the string -> TenantDataState conversion. +var TenantDataStateValues = map[string]TenantDataState{ + "ready": DataStateReady, + "add": DataStateAdd, + "drop": DataStateDrop, +} + +// ExtendedTenantInfo captures both a TenantInfo and the ExtraColumns +// that go alongside it. +type ExtendedTenantInfo struct { + TenantInfo + TenantInfoWithUsage_ExtraColumns +} + +// ToExtended converts a TenantInfoWithUsage to an ExtendedTenantInfo. +func (m *TenantInfoWithUsage) ToExtended() *ExtendedTenantInfo { + return &ExtendedTenantInfo{ + TenantInfo: m.TenantInfo, + TenantInfoWithUsage_ExtraColumns: m.TenantInfoWithUsage_ExtraColumns, + } +} diff --git a/pkg/sql/catalog/descpb/tenant.proto b/pkg/sql/catalog/descpb/tenant.proto index 56dfe77a640c..1671ecac9b87 100644 --- a/pkg/sql/catalog/descpb/tenant.proto +++ b/pkg/sql/catalog/descpb/tenant.proto @@ -22,29 +22,33 @@ import "multitenant/tenantcapabilities/tenantcapabilitiespb/capabilities.proto"; message TenantInfo { option (gogoproto.equal) = true; - // The state of the tenant. Dictates whether SQL sessions for the tenant - // should be allowed, although this is currently not enforced. - enum State { - // Tenant is online and available for SQL sessions. - ACTIVE = 0; - // Tenant is being added. Not available for SQL sessions. + // The state of the tenant's logical keyspace (DEPRECATED). + // This enum is only used when the data_state column is NULL. + enum DeprecatedDataState { + // Tenant data is ready and SQL servers can access it. + // DEPRECATED. Use DataStateReady. + READY = 0; + // Tenant data is being added. Not available for SQL sessions. + // DEPRECATED. Use DataStateAdd. ADD = 1; - // Tenant is being dropped. Not available for SQL sessions. + // Tenant data is being dropped. Not available for SQL sessions. + // DEPRECATED. Use DataStateDrop. DROP = 2; } - optional uint64 id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "ID"]; - optional State state = 2 [(gogoproto.nullable) = false]; - optional string name = 3 [ - (gogoproto.nullable) = false, - (gogoproto.customtype) = "github.com/cockroachdb/cockroach/pkg/roachpb.TenantName"]; + // ID is the internal numeric identifier of the tenant (DEPRECATED). + // This field is redundant with the id column in system.tenants. + optional uint64 deprecated_id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "DeprecatedID"]; + + // DeprecatedDataState is the state of the tenant's keyspace (DEPRECATED). + optional DeprecatedDataState deprecated_data_state = 2 [(gogoproto.nullable) = false]; // DroppedName is the name the tenant had before DROP TENANT was // run on the tenant. It should be empty for active or adding // tenants. optional string dropped_name = 4 [ (gogoproto.nullable) = false, - (gogoproto.customtype) = "github.com/cockroachdb/cockroach/pkg/roachpb.TenantName"]; + (gogoproto.casttype) = "github.com/cockroachdb/cockroach/pkg/roachpb.TenantName"]; // TenantReplicationJobID is set if this tenant is the target tenant // of a running tenant replication job. @@ -58,6 +62,9 @@ message TenantInfo { optional cockroach.multitenant.tenantcapabilitiespb.TenantCapabilities capabilities = 6 [ (gogoproto.nullable) = false ]; + + // available tag: 3. + // available tag: 7 or above. } // TenantInfoAndUsage contains the information for a tenant in a multi-tenant @@ -81,4 +88,28 @@ message TenantInfoWithUsage { } optional TenantInfo info = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; optional Usage usage = 2; + + // ExtraColumns contain the additional metadata from the other + // columns in system.tenants not otherwise encoded in the info field. + message ExtraColumns { + option (gogoproto.equal) = true; + + optional uint64 id = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "ID"]; + + // Copy of the name column in system.tenants. + optional string name = 2 [ + (gogoproto.nullable) = false, + (gogoproto.casttype) = "github.com/cockroachdb/cockroach/pkg/roachpb.TenantName"]; + + // Copy of the data_state column in system.tenants. + optional uint32 data_state = 3 [ + (gogoproto.nullable) = false, + (gogoproto.casttype) = "TenantDataState"]; + + // Copy of the service_mode column in system.tenants. + optional uint32 service_mode = 4 [ + (gogoproto.nullable) = false, + (gogoproto.casttype) = "TenantServiceMode"]; + } + optional ExtraColumns extra_columns = 3 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; } diff --git a/pkg/sql/catalog/systemschema/system.go b/pkg/sql/catalog/systemschema/system.go index 1bb8e14fbcbe..65d6df6761f0 100644 --- a/pkg/sql/catalog/systemschema/system.go +++ b/pkg/sql/catalog/systemschema/system.go @@ -100,16 +100,19 @@ CREATE TABLE system.settings ( DescIDSequenceSchema = ` CREATE SEQUENCE system.descriptor_id_seq;` - tenantNameComputeExpr = `crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo':::STRING, info)->>'name':::STRING` - TenantsTableSchema = ` + // Note: the "active" column is deprecated. + TenantsTableSchema = ` CREATE TABLE system.tenants ( - id INT8 NOT NULL, - active BOOL NOT NULL DEFAULT true, - info BYTES, - name STRING GENERATED ALWAYS AS (` + tenantNameComputeExpr + `) VIRTUAL, + id INT8 NOT NULL, + active BOOL NOT NULL DEFAULT true NOT VISIBLE, + info BYTES, + name STRING, + data_state INT, + service_mode INT, CONSTRAINT "primary" PRIMARY KEY (id), - FAMILY "primary" (id, active, info), - UNIQUE INDEX tenants_name_idx (name ASC) + FAMILY "primary" (id, active, info, name, data_state, service_mode), + UNIQUE INDEX tenants_name_idx (name ASC), + INDEX tenants_service_mode_idx (service_mode ASC) );` // RoleIDSequenceSchema starts at 100 so we have reserved IDs for special @@ -120,7 +123,6 @@ CREATE SEQUENCE system.role_id_seq START 100 MINVALUE 100 MAXVALUE 2147483647;` indexUsageComputeExpr = `(statistics->'statistics':::STRING)->'indexes':::STRING` ) -var tenantNameComputeExprStr = tenantNameComputeExpr var indexUsageComputeExprStr = indexUsageComputeExpr // These system tables are not part of the system config. @@ -1209,17 +1211,17 @@ var ( keys.TenantsTableID, []descpb.ColumnDescriptor{ {Name: "id", ID: 1, Type: types.Int}, - {Name: "active", ID: 2, Type: types.Bool, DefaultExpr: &trueBoolString}, + {Name: "active", ID: 2, Type: types.Bool, DefaultExpr: &trueBoolString, Hidden: true}, {Name: "info", ID: 3, Type: types.Bytes, Nullable: true}, - {Name: "name", ID: 4, Type: types.String, Nullable: true, - Virtual: true, - ComputeExpr: &tenantNameComputeExprStr}, + {Name: "name", ID: 4, Type: types.String, Nullable: true}, + {Name: "data_state", ID: 5, Type: types.Int, Nullable: true}, + {Name: "service_mode", ID: 6, Type: types.Int, Nullable: true}, }, []descpb.ColumnFamilyDescriptor{{ Name: "primary", ID: 0, - ColumnNames: []string{"id", "active", "info"}, - ColumnIDs: []descpb.ColumnID{1, 2, 3}, + ColumnNames: []string{"id", "active", "info", "name", "data_state", "service_mode"}, + ColumnIDs: []descpb.ColumnID{1, 2, 3, 4, 5, 6}, }}, pk("id"), descpb.IndexDescriptor{ @@ -1232,6 +1234,15 @@ var ( KeySuffixColumnIDs: []descpb.ColumnID{1}, Version: descpb.StrictIndexColumnIDGuaranteesVersion, }, + descpb.IndexDescriptor{ + Name: "tenants_service_mode_idx", + ID: 3, + KeyColumnNames: []string{"service_mode"}, + KeyColumnDirections: []catenumpb.IndexColumn_Direction{catenumpb.IndexColumn_ASC}, + KeyColumnIDs: []descpb.ColumnID{6}, + KeySuffixColumnIDs: []descpb.ColumnID{1}, + Version: descpb.StrictIndexColumnIDGuaranteesVersion, + }, )) ) 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/tenant_garbage_collection.go b/pkg/sql/gcjob/tenant_garbage_collection.go index 5defda20cb08..e51ea640d948 100644 --- a/pkg/sql/gcjob/tenant_garbage_collection.go +++ b/pkg/sql/gcjob/tenant_garbage_collection.go @@ -39,12 +39,12 @@ func gcTenant( if progress.Tenant.Status == jobspb.SchemaChangeGCProgress_WAITING_FOR_CLEAR { return errors.AssertionFailedf( - "Tenant id %d is expired and should not be in state %+v", + "tenant ID %d is expired and should not be in state %+v", tenID, jobspb.SchemaChangeGCProgress_WAITING_FOR_CLEAR, ) } - var info *descpb.TenantInfo + var info *descpb.ExtendedTenantInfo if err := execCfg.InternalDB.Txn(ctx, func(ctx context.Context, txn isql.Txn) (err error) { info, err = sql.GetTenantRecordByID(ctx, txn /* txn */, roachpb.MustMakeTenantID(tenID)) return err @@ -56,7 +56,7 @@ func gcTenant( // This will happen if the job deletes the tenant row and fails to update // its progress. In this case there's nothing to do but update the job // progress. - log.Errorf(ctx, "tenant id %d not found while attempting to GC", tenID) + log.Errorf(ctx, "tenant ID %d not found while attempting to GC", tenID) progress.Tenant.Status = jobspb.SchemaChangeGCProgress_CLEARED } return nil @@ -66,7 +66,7 @@ func gcTenant( // This case should never happen. if progress.Tenant.Status == jobspb.SchemaChangeGCProgress_CLEARED { - return errors.AssertionFailedf("GC state for tenant %+v is DELETED yet the tenant row still exists", info) + return errors.AssertionFailedf("GC state for tenant is DELETED yet the tenant row still exists: %+v", *info) } if err := sql.GCTenantSync(ctx, execCfg, info); err != nil { diff --git a/pkg/sql/gcjob_test/gc_job_test.go b/pkg/sql/gcjob_test/gc_job_test.go index 3d50388fa13d..67469265ff50 100644 --- a/pkg/sql/gcjob_test/gc_job_test.go +++ b/pkg/sql/gcjob_test/gc_job_test.go @@ -440,7 +440,7 @@ func TestGCTenant(t *testing.T) { txn, execCfg.SpanConfigKVAccessor.WithTxn(ctx, txn.KV()), &descpb.TenantInfoWithUsage{ - TenantInfo: descpb.TenantInfo{ID: activeTenID}, + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ID: activeTenID}, }, execCfg.DefaultZoneConfig, ) @@ -453,7 +453,10 @@ func TestGCTenant(t *testing.T) { txn, execCfg.SpanConfigKVAccessor.WithTxn(ctx, txn.KV()), &descpb.TenantInfoWithUsage{ - TenantInfo: descpb.TenantInfo{ID: dropTenID, State: descpb.TenantInfo_DROP}, + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ + ID: dropTenID, + DataState: descpb.DataStateDrop, + }, }, execCfg.DefaultZoneConfig, ) @@ -469,7 +472,7 @@ func TestGCTenant(t *testing.T) { require.EqualError( t, gcClosure(10, progress), - "Tenant id 10 is expired and should not be in state WAITING_FOR_CLEAR", + "tenant ID 10 is expired and should not be in state WAITING_FOR_CLEAR", ) require.Equal(t, jobspb.SchemaChangeGCProgress_WAITING_FOR_CLEAR, progress.Tenant.Status) }) @@ -493,7 +496,9 @@ func TestGCTenant(t *testing.T) { require.EqualError( t, gcClosure(dropTenID, progress), - `GC state for tenant id:11 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 is DELETED yet the tenant row still exists: `+ + `{TenantInfo:{DeprecatedID:0 DeprecatedDataState:DROP DroppedName: TenantReplicationJobID:0 Capabilities:{CanAdminSplit:false}} `+ + `TenantInfoWithUsage_ExtraColumns:{ID:11 Name:tenant-11 DataState:drop ServiceMode:external}}`, ) }) diff --git a/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog b/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog index 1c48df380a62..1ce65c4de886 100644 --- a/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog +++ b/pkg/sql/logictest/testdata/logic_test/crdb_internal_catalog @@ -109,7 +109,7 @@ SELECT id, strip_volatile(descriptor) FROM crdb_internal.kv_catalog_descriptor 5 {"table": {"columns": [{"id": 1, "name": "id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "config", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}], "formatVersion": 3, "id": 5, "name": "zones", "nextColumnId": 3, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["id"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2], "storeColumnNames": ["config"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "480", "userProto": "admin", "withGrantOption": "480"}, {"privileges": "480", "userProto": "root", "withGrantOption": "480"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} 6 {"table": {"columns": [{"id": 1, "name": "name", "type": {"family": "StringFamily", "oid": 25}}, {"id": 2, "name": "value", "type": {"family": "StringFamily", "oid": 25}}, {"defaultExpr": "now():::TIMESTAMP", "id": 3, "name": "lastUpdated", "type": {"family": "TimestampFamily", "oid": 1114}}, {"id": 4, "name": "valueType", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}], "formatVersion": 3, "id": 6, "name": "settings", "nextColumnId": 5, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["name"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2, 3, 4], "storeColumnNames": ["value", "lastUpdated", "valueType"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "480", "userProto": "admin", "withGrantOption": "480"}, {"privileges": "480", "userProto": "root", "withGrantOption": "480"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} 7 {"table": {"columns": [{"id": 1, "name": "value", "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 7, "name": "descriptor_id_seq", "parentId": 1, "primaryIndex": {"encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["value"], "name": "primary", "partitioning": {}, "sharded": {}, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "admin", "withGrantOption": "32"}, {"privileges": "32", "userProto": "root", "withGrantOption": "32"}], "version": 2}, "replacementOf": {"time": {}}, "sequenceOpts": {"cacheSize": "1", "increment": "1", "maxValue": "9223372036854775807", "minValue": "1", "sequenceOwner": {}, "start": "1"}, "unexposedParentSchemaId": 29, "version": "1"}} -8 {"table": {"columns": [{"id": 1, "name": "id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"defaultExpr": "true", "id": 2, "name": "active", "type": {"oid": 16}}, {"id": 3, "name": "info", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"computeExpr": "crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo':::STRING, info)->>'name':::STRING", "id": 4, "name": "name", "nullable": true, "type": {"family": "StringFamily", "oid": 25}, "virtual": true}], "formatVersion": 3, "id": 8, "indexes": [{"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 2, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [4], "keyColumnNames": ["name"], "keySuffixColumnIds": [1], "name": "tenants_name_idx", "partitioning": {}, "sharded": {}, "unique": true, "version": 3}], "name": "tenants", "nextColumnId": 5, "nextConstraintId": 3, "nextIndexId": 3, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 2, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["id"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2, 3], "storeColumnNames": ["active", "info"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "admin", "withGrantOption": "32"}, {"privileges": "32", "userProto": "root", "withGrantOption": "32"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} +8 {"table": {"columns": [{"id": 1, "name": "id", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"defaultExpr": "true", "hidden": true, "id": 2, "name": "active", "type": {"oid": 16}}, {"id": 3, "name": "info", "nullable": true, "type": {"family": "BytesFamily", "oid": 17}}, {"id": 4, "name": "name", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"id": 5, "name": "data_state", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "service_mode", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 8, "indexes": [{"constraintId": 1, "foreignKey": {}, "geoConfig": {}, "id": 2, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [4], "keyColumnNames": ["name"], "keySuffixColumnIds": [1], "name": "tenants_name_idx", "partitioning": {}, "sharded": {}, "unique": true, "version": 3}, {"foreignKey": {}, "geoConfig": {}, "id": 3, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [6], "keyColumnNames": ["service_mode"], "keySuffixColumnIds": [1], "name": "tenants_service_mode_idx", "partitioning": {}, "sharded": {}, "version": 3}], "name": "tenants", "nextColumnId": 7, "nextConstraintId": 3, "nextIndexId": 4, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 2, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC"], "keyColumnIds": [1], "keyColumnNames": ["id"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2, 3, 4, 5, 6], "storeColumnNames": ["active", "info", "name", "data_state", "service_mode"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "32", "userProto": "admin", "withGrantOption": "32"}, {"privileges": "32", "userProto": "root", "withGrantOption": "32"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} 11 {"table": {"columns": [{"id": 1, "name": "descID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 2, "name": "version", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 3, "name": "nodeID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 4, "name": "expiration", "type": {"family": "TimestampFamily", "oid": 1114}}], "formatVersion": 3, "id": 11, "name": "lease", "nextColumnId": 5, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC", "ASC", "ASC", "ASC"], "keyColumnIds": [1, 2, 4, 3], "keyColumnNames": ["descID", "version", "expiration", "nodeID"], "name": "primary", "partitioning": {}, "sharded": {}, "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "480", "userProto": "admin", "withGrantOption": "480"}, {"privileges": "480", "userProto": "root", "withGrantOption": "480"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} 12 {"table": {"columns": [{"id": 1, "name": "timestamp", "type": {"family": "TimestampFamily", "oid": 1114}}, {"id": 2, "name": "eventType", "type": {"family": "StringFamily", "oid": 25}}, {"id": 3, "name": "targetID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 4, "name": "reportingID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 5, "name": "info", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"defaultExpr": "uuid_v4()", "id": 6, "name": "uniqueID", "type": {"family": "BytesFamily", "oid": 17}}], "formatVersion": 3, "id": 12, "name": "eventlog", "nextColumnId": 7, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC", "ASC"], "keyColumnIds": [1, 6], "keyColumnNames": ["timestamp", "uniqueID"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2, 3, 4, 5], "storeColumnNames": ["eventType", "targetID", "reportingID", "info"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "480", "userProto": "admin", "withGrantOption": "480"}, {"privileges": "480", "userProto": "root", "withGrantOption": "480"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} 13 {"table": {"columns": [{"id": 1, "name": "timestamp", "type": {"family": "TimestampFamily", "oid": 1114}}, {"id": 2, "name": "rangeID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 3, "name": "storeID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 4, "name": "eventType", "type": {"family": "StringFamily", "oid": 25}}, {"id": 5, "name": "otherRangeID", "nullable": true, "type": {"family": "IntFamily", "oid": 20, "width": 64}}, {"id": 6, "name": "info", "nullable": true, "type": {"family": "StringFamily", "oid": 25}}, {"defaultExpr": "unique_rowid()", "id": 7, "name": "uniqueID", "type": {"family": "IntFamily", "oid": 20, "width": 64}}], "formatVersion": 3, "id": 13, "name": "rangelog", "nextColumnId": 8, "nextConstraintId": 2, "nextIndexId": 2, "nextMutationId": 1, "parentId": 1, "primaryIndex": {"constraintId": 1, "encodingType": 1, "foreignKey": {}, "geoConfig": {}, "id": 1, "interleave": {}, "keyColumnDirections": ["ASC", "ASC"], "keyColumnIds": [1, 7], "keyColumnNames": ["timestamp", "uniqueID"], "name": "primary", "partitioning": {}, "sharded": {}, "storeColumnIds": [2, 3, 4, 5, 6], "storeColumnNames": ["rangeID", "storeID", "eventType", "otherRangeID", "info"], "unique": true, "version": 4}, "privileges": {"ownerProto": "node", "users": [{"privileges": "480", "userProto": "admin", "withGrantOption": "480"}, {"privileges": "480", "userProto": "root", "withGrantOption": "480"}], "version": 2}, "replacementOf": {"time": {}}, "unexposedParentSchemaId": 29, "version": "1"}} diff --git a/pkg/sql/logictest/testdata/logic_test/information_schema b/pkg/sql/logictest/testdata/logic_test/information_schema index b71ba9af19f8..08bfb2775ebf 100644 --- a/pkg/sql/logictest/testdata/logic_test/information_schema +++ b/pkg/sql/logictest/testdata/logic_test/information_schema @@ -1903,7 +1903,6 @@ system public 29_6_2_not_null system public 29_6_3_not_null lastUpdated IS NOT NULL system public 29_7_1_not_null value IS NOT NULL system public 29_8_1_not_null id IS NOT NULL -system public 29_8_2_not_null active IS NOT NULL system public check_bounds ((start_key < end_key)) system public check_crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8 ((crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_plan_hash_transaction_fingerprint_id_shard_8 IN (0:::INT8, 1:::INT8, 2:::INT8, 3:::INT8, 4:::INT8, 5:::INT8, 6:::INT8, 7:::INT8))) system public check_crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8 ((crdb_internal_aggregated_ts_app_name_fingerprint_id_node_id_shard_8 IN (0:::INT8, 1:::INT8, 2:::INT8, 3:::INT8, 4:::INT8, 5:::INT8, 6:::INT8, 7:::INT8))) @@ -2310,9 +2309,11 @@ system public tenant_usage ru_refill_rate system public tenant_usage tenant_id 1 system public tenant_usage total_consumption 9 system public tenants active 2 +system public tenants data_state 5 system public tenants id 1 system public tenants info 3 system public tenants name 4 +system public tenants service_mode 6 system public transaction_statistics agg_interval 5 system public transaction_statistics aggregated_ts 1 system public transaction_statistics app_name 3 diff --git a/pkg/sql/logictest/testdata/logic_test/pg_catalog b/pkg/sql/logictest/testdata/logic_test/pg_catalog index 8d0e7c822ede..a63d1e9305b9 100644 --- a/pkg/sql/logictest/testdata/logic_test/pg_catalog +++ b/pkg/sql/logictest/testdata/logic_test/pg_catalog @@ -1147,6 +1147,7 @@ indexrelid indrelid indnatts indisunique indnullsnotdistinct indisprimary 2148104569 21 2 true false true false true false true false false true false 1 2 3403232968 3403232968 0 0 2 2 NULL NULL 2 2268653844 40 4 true false true false true false true false false true false 1 2 3 4 0 0 0 0 0 0 0 0 2 2 2 2 NULL NULL 4 2361445172 8 1 true false true false true false true false false true false 1 0 0 2 NULL NULL 1 +2361445174 8 1 false false false false false false true false false true false 6 0 0 2 NULL NULL 1 2361445175 8 1 true false false false true false true false false true false 4 3403232968 0 2 NULL NULL 1 2407840836 24 3 true false true false true false true false false true false 1 2 3 0 0 0 0 0 0 2 2 2 NULL NULL 3 2528390115 47 1 true false true false true false true false false true false 1 0 0 2 NULL NULL 1 @@ -1244,6 +1245,7 @@ indexrelid operator_argument_type_oid operator_argument_position 2268653844 0 3 2268653844 0 4 2361445172 0 1 +2361445174 0 1 2361445175 0 1 2407840836 0 1 2407840836 0 2 diff --git a/pkg/sql/logictest/testdata/logic_test/tenant b/pkg/sql/logictest/testdata/logic_test/tenant index af2ee17092f0..aec4c7f903bc 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 10 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}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-one {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} -3 true two {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} -4 true three {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "3", "droppedName": "", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "4", "droppedName": "", "tenantReplicationJobId": "0"} -query ITT colnames +query ITTT colnames SHOW TENANT system ---- -id name status -1 system ACTIVE +id name data_state service_mode +1 system ready shared -query ITT colnames +query ITTT colnames SHOW TENANT "tenant-one" ---- -id name status -2 tenant-one ACTIVE +id name data_state service_mode +2 tenant-one ready none -query ITT colnames +query ITTT colnames SHOW TENANT "two" ---- -id name status -3 two ACTIVE +id name data_state service_mode +3 two ready none -query ITT colnames +query ITTT colnames SHOW TENANT two ---- -id name status -3 two ACTIVE +id name data_state service_mode +3 two ready none -query ITT colnames +query ITTT colnames SHOW TENANT three ---- -id name status -4 three ACTIVE +id name data_state service_mode +4 three ready none -query ITT colnames +query ITTT colnames SHOW TENANTS ---- -id name status -1 system ACTIVE -2 tenant-one ACTIVE -3 two ACTIVE -4 three ACTIVE +id name data_state service_mode +1 system ready shared +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 ACTIVE +id name data_state 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 ACTIVE +id name data_state 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}, "droppedName": "", "id": "5", "name": "four", "state": "ACTIVE", "tenantReplicationJobId": "0"} +5 true four {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "5", "droppedName": "", "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}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-one {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-one", "state": "ACTIVE", "tenantReplicationJobId": "0"} -3 true two {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "3", "name": "two", "state": "ACTIVE", "tenantReplicationJobId": "0"} -4 true three {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "4", "name": "three", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "four", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -6 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "five-requiring-quotes", "id": "6", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -7 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "to-be-reclaimed", "id": "7", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -8 true to-be-reclaimed {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "8", "name": "to-be-reclaimed", "state": "ACTIVE", "tenantReplicationJobId": "0"} +1 true system {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 true tenant-one {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +3 true two {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "3", "droppedName": "", "tenantReplicationJobId": "0"} +4 true three {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "4", "droppedName": "", "tenantReplicationJobId": "0"} +5 false NULL {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "5", "droppedName": "four", "tenantReplicationJobId": "0"} +6 false NULL {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "6", "droppedName": "five-requiring-quotes", "tenantReplicationJobId": "0"} +7 false NULL {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "7", "droppedName": "to-be-reclaimed", "tenantReplicationJobId": "0"} +8 true to-be-reclaimed {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "8", "droppedName": "", "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 ACTIVE -2 tenant-one ACTIVE -3 two ACTIVE -4 three ACTIVE -8 to-be-reclaimed ACTIVE -9 1 ACTIVE -10 a-b ACTIVE -11 hello-100 ACTIVE +id name data_state service_mode +1 system ready shared +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 89df728b0f10..c2d68068ba0f 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 10 system # Create three tenants. @@ -12,6 +12,11 @@ SELECT crdb_internal.create_tenant(5) ---- 5 +# create_tenant auto-sets the service for backward-compatibility. +# Reset it here so the tests below don't get confused. +statement ok +ALTER TENANT [5] STOP SERVICE + query error invalid tenant name SELECT crdb_internal.create_tenant(10, 'ABC') @@ -31,16 +36,17 @@ SELECT crdb_internal.create_tenant('tenant-number-eleven') ---- 2 -query IBTT colnames -SELECT id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) +query IBTIIT colnames +SELECT id, active, name, data_state, service_mode, + crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants ORDER BY id ---- -id active name crdb_internal.pb_to_json -1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 true tenant-5 {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "5", "name": "tenant-5", "state": "ACTIVE", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +id active name data_state service_mode crdb_internal.pb_to_json +1 true system 0 2 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +5 true tenant-5 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "5", "droppedName": "", "tenantReplicationJobId": "0"} +10 true tenant-number-ten 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "10", "droppedName": "", "tenantReplicationJobId": "0"} # Check we can add a name where none existed before. statement ok @@ -93,7 +99,7 @@ id active name # Garbage collect a non-drop tenant fails. -query error tenant 5 is not in state DROP +query error tenant 5 is not in data state DROP SELECT crdb_internal.gc_tenant(5) # Note this just marks the tenant as dropped but does not call GC. @@ -101,16 +107,17 @@ SELECT crdb_internal.gc_tenant(5) statement ok DROP TENANT [5] -query IBTT colnames -SELECT id, active, name, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) +query IBTIIT colnames +SELECT id, active, name, data_state, service_mode, + crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants ORDER BY id ---- -id active name crdb_internal.pb_to_json -1 true system {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true tenant-number-eleven {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -5 false NULL {"capabilities": {"canAdminSplit": false}, "droppedName": "my-new-tenant-name", "id": "5", "name": "", "state": "DROP", "tenantReplicationJobId": "0"} -10 true tenant-number-ten {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +id active name data_state service_mode crdb_internal.pb_to_json +1 true system 0 2 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +5 false NULL 2 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "DROP", "deprecatedId": "5", "droppedName": "my-new-tenant-name", "tenantReplicationJobId": "0"} +10 true tenant-number-ten 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "10", "droppedName": "", "tenantReplicationJobId": "0"} # Try to recreate an existing tenant. @@ -211,15 +218,16 @@ succeeded statement error pgcode 42704 tenant "5" does not exist DROP TENANT [5] -query IBT colnames -SELECT id, active, crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) +query IBTIIT colnames +SELECT id, active, name, data_state, service_mode, + crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true) FROM system.tenants ORDER BY id ---- -id active crdb_internal.pb_to_json -1 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "1", "name": "system", "state": "ACTIVE", "tenantReplicationJobId": "0"} -2 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "2", "name": "tenant-number-eleven", "state": "ACTIVE", "tenantReplicationJobId": "0"} -10 true {"capabilities": {"canAdminSplit": false}, "droppedName": "", "id": "10", "name": "tenant-number-ten", "state": "ACTIVE", "tenantReplicationJobId": "0"} +id active name data_state service_mode crdb_internal.pb_to_json +1 true system 0 2 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "1", "droppedName": "", "tenantReplicationJobId": "0"} +2 true tenant-number-eleven 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "2", "droppedName": "", "tenantReplicationJobId": "0"} +10 true tenant-number-ten 0 1 {"capabilities": {"canAdminSplit": false}, "deprecatedDataState": "READY", "deprecatedId": "10", "droppedName": "", "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/planhook.go b/pkg/sql/planhook.go index 5b43c1a5c82c..7808f7562cd9 100644 --- a/pkg/sql/planhook.go +++ b/pkg/sql/planhook.go @@ -126,7 +126,7 @@ type PlanHookState interface { SpanConfigReconciler() spanconfig.Reconciler BufferClientNotice(ctx context.Context, notice pgnotice.Notice) Txn() *kv.Txn - LookupTenantInfo(ctx context.Context, tenantSpec *tree.TenantSpec, op string) (*descpb.TenantInfo, error) + LookupTenantInfo(ctx context.Context, tenantSpec *tree.TenantSpec, op string) (*descpb.ExtendedTenantInfo, error) GetAvailableTenantID(ctx context.Context, name roachpb.TenantName) (roachpb.TenantID, error) InternalSQLTxn() descs.Txn } 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 fd087c95a231..6f58db9bda42 100644 --- a/pkg/sql/show_tenant.go +++ b/pkg/sql/show_tenant.go @@ -13,6 +13,7 @@ package sql import ( "context" "fmt" + "strings" "time" "github.com/cockroachdb/cockroach/pkg/jobs" @@ -31,16 +32,16 @@ import ( type tenantStatus string const ( - initReplication tenantStatus = "INITIALIZING REPLICATION" - replicating tenantStatus = "REPLICATING" - replicationPaused tenantStatus = "REPLICATION PAUSED" - cuttingOver tenantStatus = "REPLICATION CUTTING OVER" + initReplication tenantStatus = "initializing replication" + replicating tenantStatus = "replicating" + replicationPaused tenantStatus = "replication paused" + cuttingOver tenantStatus = "replication cutting over" // Users should not see this status normally. - replicationUnknownFormat tenantStatus = "REPLICATION UNKNOWN (%s)" + replicationUnknownFormat tenantStatus = "replication unknown (%s)" ) type tenantValues struct { - tenantInfo *descpb.TenantInfo + tenantInfo *descpb.ExtendedTenantInfo tenantStatus tenantStatus replicationInfo *streampb.StreamIngestionStats protectedTimestamp hlc.Timestamp @@ -166,7 +167,7 @@ func getTenantStatus( } func (n *showTenantNode) getTenantValues( - params runParams, tenantInfo *descpb.TenantInfo, + params runParams, tenantInfo *descpb.ExtendedTenantInfo, ) (*tenantValues, error) { var values tenantValues values.tenantInfo = tenantInfo @@ -176,12 +177,13 @@ func (n *showTenantNode) getTenantValues( if n.withReplication { return nil, errors.Newf("tenant %q does not have an active replication job", tenantInfo.Name) } - values.tenantStatus = tenantStatus(values.tenantInfo.State.String()) + dataState := strings.ToLower(values.tenantInfo.DataState.String()) + values.tenantStatus = tenantStatus(dataState) return &values, nil } - switch values.tenantInfo.State { - case descpb.TenantInfo_ADD: + switch values.tenantInfo.DataState { + case descpb.DataStateAdd: // There is a replication job, we need to get the job info and the // replication stats in order to generate the exact tenant status. registry := params.p.execCfg.JobRegistry @@ -205,10 +207,11 @@ func (n *showTenantNode) getTenantValues( } values.tenantStatus = getTenantStatus(job.Status(), values.replicationInfo) - case descpb.TenantInfo_ACTIVE, descpb.TenantInfo_DROP: - values.tenantStatus = tenantStatus(values.tenantInfo.State.String()) + case descpb.DataStateReady, descpb.DataStateDrop: + dataState := values.tenantInfo.DataState.String() + values.tenantStatus = tenantStatus(dataState) default: - return nil, errors.Newf("tenant %q state is unknown: %s", tenantInfo.Name, values.tenantInfo.State.String()) + return nil, errors.Newf("tenant %q state is unknown: %s", tenantInfo.Name, values.tenantInfo.DataState) } return &values, nil } @@ -239,6 +242,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(strings.ToLower(tenantInfo.ServiceMode.String())), } if n.withReplication { diff --git a/pkg/sql/tenant_accessors.go b/pkg/sql/tenant_accessors.go index de4bfdae051d..d3d3940d23dd 100644 --- a/pkg/sql/tenant_accessors.go +++ b/pkg/sql/tenant_accessors.go @@ -55,11 +55,10 @@ func rejectIfSystemTenant(tenID uint64, op string) error { func GetAllNonDropTenantIDs(ctx context.Context, txn isql.Txn) ([]roachpb.TenantID, error) { rows, err := txn.QueryBuffered( ctx, "get-tenant-ids", txn.KV(), ` - SELECT id - FROM system.tenants - WHERE crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info, true)->>'state' != 'DROP' - ORDER BY id - `) +SELECT id +FROM system.tenants +WHERE data_state != $1 +ORDER BY id`, descpb.DataStateDrop) if err != nil { return nil, err } @@ -82,36 +81,76 @@ func GetAllNonDropTenantIDs(ctx context.Context, txn isql.Txn) ([]roachpb.Tenant // system.tenants. func GetTenantRecordByName( ctx context.Context, settings *cluster.Settings, txn isql.Txn, tenantName roachpb.TenantName, -) (*descpb.TenantInfo, error) { - if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNames) { +) (*descpb.ExtendedTenantInfo, error) { + if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNamesStateAndServiceMode) { return nil, errors.Newf("tenant names not supported until upgrade to %s or higher is completed", - clusterversion.V23_1TenantNames.String()) + clusterversion.V23_1TenantNamesStateAndServiceMode.String()) } row, err := txn.QueryRowEx( ctx, "get-tenant", txn.KV(), sessiondata.NodeUserSessionDataOverride, - `SELECT info FROM system.tenants WHERE name = $1`, tenantName, + `SELECT id, info, name, data_state, service_mode +FROM system.tenants WHERE name = $1`, tenantName, ) if err != nil { return nil, err } else if row == nil { return nil, pgerror.Newf(pgcode.UndefinedObject, "tenant %q does not exist", tenantName) } + return getTenantInfoFromRow(row) +} + +func getTenantInfoFromRow(row tree.Datums) (*descpb.ExtendedTenantInfo, error) { + info := &descpb.ExtendedTenantInfo{} + info.ID = uint64(tree.MustBeDInt(row[0])) - info := &descpb.TenantInfo{} - infoBytes := []byte(tree.MustBeDBytes(row[0])) - if err := protoutil.Unmarshal(infoBytes, info); err != nil { + // For the benefit of pre-23.1 BACKUP/RESTORE. + info.DeprecatedID = info.ID + + infoBytes := []byte(tree.MustBeDBytes(row[1])) + if err := protoutil.Unmarshal(infoBytes, &info.TenantInfo); err != nil { return nil, err } + + // Load the name if defined. + if row[2] != tree.DNull { + info.Name = roachpb.TenantName(tree.MustBeDString(row[2])) + } + + // Load the data state column if defined. + if row[3] != tree.DNull { + info.DataState = descpb.TenantDataState(tree.MustBeDInt(row[3])) + } else { + // Pre-v23.1 info struct. + switch info.TenantInfo.DeprecatedDataState { + case descpb.TenantInfo_READY: + info.DataState = descpb.DataStateReady + case descpb.TenantInfo_ADD: + info.DataState = descpb.DataStateAdd + case descpb.TenantInfo_DROP: + info.DataState = descpb.DataStateDrop + return nil, errors.AssertionFailedf("unhandled: %d", info.TenantInfo.DeprecatedDataState) + } + } + + // Load the service mode if defined. + info.ServiceMode = descpb.ServiceModeNone + if row[4] != tree.DNull { + info.ServiceMode = descpb.TenantServiceMode(tree.MustBeDInt(row[4])) + } else if info.DataState == descpb.DataStateReady { + // Records created for CC Serverless pre-v23.1. + info.ServiceMode = descpb.ServiceModeExternal + } return info, nil } // GetTenantRecordByID retrieves a tenant in system.tenants. func GetTenantRecordByID( ctx context.Context, txn isql.Txn, tenID roachpb.TenantID, -) (*descpb.TenantInfo, error) { +) (*descpb.ExtendedTenantInfo, error) { row, err := txn.QueryRowEx( ctx, "get-tenant", txn.KV(), sessiondata.NodeUserSessionDataOverride, - `SELECT info FROM system.tenants WHERE id = $1`, tenID.ToUint64(), + `SELECT id, info, name, data_state, service_mode +FROM system.tenants WHERE id = $1`, tenID.ToUint64(), ) if err != nil { return nil, err @@ -119,12 +158,7 @@ func GetTenantRecordByID( return nil, pgerror.Newf(pgcode.UndefinedObject, "tenant \"%d\" does not exist", tenID.ToUint64()) } - info := &descpb.TenantInfo{} - infoBytes := []byte(tree.MustBeDBytes(row[0])) - if err := protoutil.Unmarshal(infoBytes, info); err != nil { - return nil, err - } - return info, nil + return getTenantInfoFromRow(row) } // LookupTenantID implements the tree.TenantOperator interface. diff --git a/pkg/sql/tenant_creation.go b/pkg/sql/tenant_creation.go index a76d53d197ec..5956d8cda69f 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.ServiceModeNone + if ctcfg.ServiceMode != nil { + v, ok := descpb.TenantServiceModeValues[strings.ToLower(*ctcfg.ServiceMode)] + if !ok { + return tid, pgerror.Newf(pgcode.Syntax, "unknown service mode: %q", *ctcfg.ServiceMode) + } + serviceMode = v + } // tenantID uint64, name roachpb.TenantName, if p.EvalContext().TxnReadOnly { @@ -87,12 +96,13 @@ func (p *planner) createTenantInternal( } info := &descpb.TenantInfoWithUsage{ - TenantInfo: descpb.TenantInfo{ + TenantInfoWithUsage_ExtraColumns: descpb.TenantInfoWithUsage_ExtraColumns{ ID: tenantID, // We synchronously initialize the tenant's keyspace below, so - // we can skip the ADD state and go straight to an ACTIVE state. - State: descpb.TenantInfo_ACTIVE, - Name: name, + // we can skip the ADD state and go straight to the READY state. + DataState: descpb.DataStateReady, + Name: name, + ServiceMode: serviceMode, }, } @@ -229,7 +239,7 @@ func CreateTenantRecord( return roachpb.TenantID{}, err } if info.Name != "" { - if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNames) { + if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNamesStateAndServiceMode) { return roachpb.TenantID{}, pgerror.Newf(pgcode.FeatureNotSupported, "cannot use tenant names") } if err := info.Name.IsValid(); err != nil { @@ -249,27 +259,49 @@ func CreateTenantRecord( if info.Name == "" { // No name: generate one if we are at the appropriate version. - if settings.Version.IsActive(ctx, clusterversion.V23_1TenantNames) { + if settings.Version.IsActive(ctx, clusterversion.V23_1TenantNamesStateAndServiceMode) { info.Name = roachpb.TenantName(fmt.Sprintf("tenant-%d", info.ID)) } } - active := info.State == descpb.TenantInfo_ACTIVE + // Populate the deprecated DataState field for compatibility + // with pre-v23.1 servers. + switch info.DataState { + case descpb.DataStateReady: + info.DeprecatedDataState = descpb.TenantInfo_READY + case descpb.DataStateAdd: + info.DeprecatedDataState = descpb.TenantInfo_ADD + case descpb.DataStateDrop: + info.DeprecatedDataState = descpb.TenantInfo_DROP + default: + return roachpb.TenantID{}, errors.AssertionFailedf("unhandled: %d", info.DataState) + } + // DeprecatedID is populated for the benefit of pre-v23.1 servers. + info.DeprecatedID = info.ID + + // active is an obsolete column preserved for compatibility with + // pre-v23.1 servers. + active := info.DataState == descpb.DataStateReady + infoBytes, err := protoutil.Marshal(&info.TenantInfo) if err != nil { return roachpb.TenantID{}, err } // Insert into the tenant table and detect collisions. + var name tree.Datum if info.Name != "" { - if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNames) { + if !settings.Version.IsActive(ctx, clusterversion.V23_1TenantNamesStateAndServiceMode) { return roachpb.TenantID{}, pgerror.Newf(pgcode.FeatureNotSupported, "cannot use tenant names") } + name = tree.NewDString(string(info.Name)) + } else { + name = tree.DNull } if num, err := txn.ExecEx( ctx, "create-tenant", txn.KV(), sessiondata.NodeUserSessionDataOverride, - `INSERT INTO system.tenants (id, active, info) VALUES ($1, $2, $3)`, - tenID, active, infoBytes, + `INSERT INTO system.tenants (id, active, info, name, data_state, service_mode) VALUES ($1, $2, $3, $4, $5, $6)`, + tenID, active, infoBytes, name, info.DataState, info.ServiceMode, ); err != nil { if pgerror.GetPGCode(err) == pgcode.UniqueViolation { extra := redact.RedactableString("") diff --git a/pkg/sql/tenant_deletion.go b/pkg/sql/tenant_deletion.go index 74ec422b116b..dd85483b3e23 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, ) } @@ -68,8 +71,9 @@ func dropTenantInternal( jobRegistry *jobs.Registry, sessionJobs *jobsCollection, user username.SQLUsername, - info *descpb.TenantInfo, + info *descpb.ExtendedTenantInfo, synchronousImmediateDrop bool, + ignoreServiceMode bool, ) error { const op = "destroy" tenID := info.ID @@ -77,8 +81,14 @@ func dropTenantInternal( return err } - if info.State == descpb.TenantInfo_DROP { - return errors.Errorf("tenant %d is already in state DROP", tenID) + if !ignoreServiceMode && info.ServiceMode != descpb.ServiceModeNone { + 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.DataStateDrop { + return errors.Errorf("tenant %q (%d) is already in data state DROP", info.Name, tenID) } // Mark the tenant as dropping. @@ -97,7 +107,7 @@ func dropTenantInternal( // TODO(ssd): We may want to implement a job that waits out // any running sql pods before enqueing the GC job. - info.State = descpb.TenantInfo_DROP + info.DataState = descpb.DataStateDrop info.DroppedName = info.Name info.Name = "" if err := UpdateTenantRecord(ctx, settings, txn, info); err != nil { diff --git a/pkg/sql/tenant_gc.go b/pkg/sql/tenant_gc.go index 49aba9cb4180..afbe14547f5c 100644 --- a/pkg/sql/tenant_gc.go +++ b/pkg/sql/tenant_gc.go @@ -30,7 +30,9 @@ import ( // // The caller is responsible for checking that the user is authorized // to take this action. -func GCTenantSync(ctx context.Context, execCfg *ExecutorConfig, info *descpb.TenantInfo) error { +func GCTenantSync( + ctx context.Context, execCfg *ExecutorConfig, info *descpb.ExtendedTenantInfo, +) error { const op = "gc" if err := rejectIfCantCoordinateMultiTenancy(execCfg.Codec, op); err != nil { return err @@ -104,9 +106,11 @@ func GCTenantSync(ctx context.Context, execCfg *ExecutorConfig, info *descpb.Ten } // clearTenant deletes the tenant's data. -func clearTenant(ctx context.Context, execCfg *ExecutorConfig, info *descpb.TenantInfo) error { +func clearTenant( + ctx context.Context, execCfg *ExecutorConfig, info *descpb.ExtendedTenantInfo, +) error { // Confirm tenant is ready to be cleared. - if info.State != descpb.TenantInfo_DROP { + if info.DataState != descpb.DataStateDrop { return errors.Errorf("tenant %d is not in state DROP", info.ID) } @@ -145,8 +149,8 @@ func (p *planner) GCTenant(ctx context.Context, tenID uint64) error { } // Confirm tenant is ready to be cleared. - if info.State != descpb.TenantInfo_DROP { - return errors.Errorf("tenant %d is not in state DROP", info.ID) + if info.DataState != descpb.DataStateDrop { + return errors.Errorf("tenant %d is not in data state DROP", info.ID) } _, err = createGCTenantJob( diff --git a/pkg/sql/tenant_service.go b/pkg/sql/tenant_service.go new file mode 100644 index 000000000000..a886cd16a4fc --- /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.TenantServiceMode +} + +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.TenantServiceMode + switch n.Command { + case tree.TenantStopService: + newMode = descpb.ServiceModeNone + case tree.TenantStartServiceExternal: + newMode = descpb.ServiceModeExternal + case tree.TenantStartServiceShared: + newMode = descpb.ServiceModeShared + 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_spec.go b/pkg/sql/tenant_spec.go index c5fbdf93707a..26c7d0533738 100644 --- a/pkg/sql/tenant_spec.go +++ b/pkg/sql/tenant_spec.go @@ -26,7 +26,7 @@ import ( type tenantSpec interface { fmt.Stringer - getTenantInfo(ctx context.Context, p *planner) (ret *descpb.TenantInfo, err error) + getTenantInfo(ctx context.Context, p *planner) (ret *descpb.ExtendedTenantInfo, err error) getTenantParameters(ctx context.Context, p *planner) (tid roachpb.TenantID, tenantName roachpb.TenantName, err error) } @@ -122,13 +122,13 @@ func (ts *tenantSpecId) getTenantParameters( func (tenantSpecAll) getTenantInfo( ctx context.Context, p *planner, -) (ret *descpb.TenantInfo, err error) { +) (ret *descpb.ExtendedTenantInfo, err error) { return nil, errors.AssertionFailedf("programming error: cannot use all in this context") } func (ts *tenantSpecName) getTenantInfo( ctx context.Context, p *planner, -) (ret *descpb.TenantInfo, err error) { +) (ret *descpb.ExtendedTenantInfo, err error) { _, tenantName, err := ts.getTenantParameters(ctx, p) if err != nil { return nil, err @@ -138,7 +138,7 @@ func (ts *tenantSpecName) getTenantInfo( func (ts *tenantSpecId) getTenantInfo( ctx context.Context, p *planner, -) (ret *descpb.TenantInfo, err error) { +) (ret *descpb.ExtendedTenantInfo, err error) { tid, _, err := ts.getTenantParameters(ctx, p) if err != nil { return nil, err @@ -149,7 +149,7 @@ func (ts *tenantSpecId) getTenantInfo( // LookupTenantInfo implements PlanHookState for the benefits of CCL statements. func (p *planner) LookupTenantInfo( ctx context.Context, ts *tree.TenantSpec, op string, -) (*descpb.TenantInfo, error) { +) (*descpb.ExtendedTenantInfo, error) { tspec, err := p.planTenantSpec(ctx, ts, op) if err != nil { return nil, err 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 e37bcaa5175c..a19b2c6daad3 100644 --- a/pkg/sql/tenant_update.go +++ b/pkg/sql/tenant_update.go @@ -22,6 +22,7 @@ import ( "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/sql/sem/tree" "github.com/cockroachdb/cockroach/pkg/sql/sessiondata" "github.com/cockroachdb/cockroach/pkg/util/log/logcrash" "github.com/cockroachdb/cockroach/pkg/util/protoutil" @@ -32,37 +33,68 @@ import ( // // Caller is expected to check the user's permission. func UpdateTenantRecord( - ctx context.Context, settings *cluster.Settings, txn isql.Txn, info *descpb.TenantInfo, + ctx context.Context, settings *cluster.Settings, txn isql.Txn, info *descpb.ExtendedTenantInfo, ) error { if err := validateTenantInfo(info); err != nil { return err } - tenID := info.ID - active := info.State == descpb.TenantInfo_ACTIVE - infoBytes, err := protoutil.Marshal(info) + // Populate the deprecated DataState field for compatibility + // with pre-v23.1 servers. + switch info.DataState { + case descpb.DataStateReady: + info.DeprecatedDataState = descpb.TenantInfo_READY + case descpb.DataStateAdd: + info.DeprecatedDataState = descpb.TenantInfo_ADD + case descpb.DataStateDrop: + info.DeprecatedDataState = descpb.TenantInfo_DROP + default: + return errors.AssertionFailedf("unhandled: %d", info.DataState) + } + // For the benefit of pre-v23.1 servers. + info.DeprecatedID = info.ID + + infoBytes, err := protoutil.Marshal(&info.TenantInfo) if err != nil { return err } + // active is a deprecated column preserved for compatibiliy + // with pre-v23.1. + active := info.DataState == descpb.DataStateReady + var name tree.Datum + if info.Name != "" { + name = tree.NewDString(string(info.Name)) + } else { + name = tree.DNull + } if num, err := txn.ExecEx( - ctx, "activate-tenant", txn.KV(), sessiondata.NodeUserSessionDataOverride, - `UPDATE system.tenants SET active = $2, info = $3 WHERE id = $1`, - tenID, active, infoBytes, + ctx, "update-tenant", txn.KV(), sessiondata.NodeUserSessionDataOverride, + `UPDATE system.tenants +SET active = $2, info = $3, name = $4, data_state = $5, service_mode = $6 +WHERE id = $1`, + info.ID, active, infoBytes, name, info.DataState, info.ServiceMode, ); 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) } return nil } -func validateTenantInfo(info *descpb.TenantInfo) error { - if info.TenantReplicationJobID != 0 && info.State == descpb.TenantInfo_ACTIVE { - return errors.Newf("tenant in state %v with replication job ID %d", info.State, info.TenantReplicationJobID) +func validateTenantInfo(info *descpb.ExtendedTenantInfo) error { + if info.TenantReplicationJobID != 0 && info.DataState == descpb.DataStateReady { + return errors.Newf("tenant in data state %v with replication job ID %d", info.DataState, info.TenantReplicationJobID) + } + if info.DroppedName != "" && info.DataState != descpb.DataStateDrop { + return errors.Newf("tenant in data state %v with dropped name %q", info.DataState, info.DroppedName) } - if info.DroppedName != "" && info.State != descpb.TenantInfo_DROP { - return errors.Newf("tenant in state %v with dropped name %q", info.State, info.DroppedName) + if info.ServiceMode != descpb.ServiceModeNone && info.DataState != descpb.DataStateReady { + return errors.Newf("cannot use tenant service mode %v with data state %v", + info.ServiceMode, info.DataState) } return nil } @@ -70,7 +102,7 @@ func validateTenantInfo(info *descpb.TenantInfo) error { // TestingUpdateTenantRecord is a public wrapper around updateTenantRecord // intended for testing purposes. func TestingUpdateTenantRecord( - ctx context.Context, settings *cluster.Settings, txn isql.Txn, info *descpb.TenantInfo, + ctx context.Context, settings *cluster.Settings, txn isql.Txn, info *descpb.ExtendedTenantInfo, ) error { return UpdateTenantRecord(ctx, settings, txn, info) } @@ -108,7 +140,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.TenantServiceMode, ) error { const op = "activate" if err := rejectIfCantCoordinateMultiTenancy(codec, op); err != nil { @@ -125,7 +162,8 @@ func ActivateTenant( } // Mark the tenant as active. - info.State = descpb.TenantInfo_ACTIVE + info.DataState = descpb.DataStateReady + info.ServiceMode = serviceMode if err := UpdateTenantRecord(ctx, settings, txn, info); err != nil { return errors.Wrap(err, "activating tenant") } @@ -133,8 +171,41 @@ func ActivateTenant( return nil } +func (p *planner) setTenantService( + ctx context.Context, info *descpb.ExtendedTenantInfo, newMode descpb.TenantServiceMode, +) 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.ServiceModeNone && info.ServiceMode != descpb.ServiceModeNone { + 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.ExtendedTenantInfo, newName roachpb.TenantName, ) error { if p.EvalContext().TxnReadOnly { return readOnlyError("ALTER TENANT RENAME TO") @@ -146,35 +217,28 @@ 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) } - if !p.EvalContext().Settings.Version.IsActive(ctx, clusterversion.V23_1TenantNames) { + if !p.EvalContext().Settings.Version.IsActive(ctx, clusterversion.V23_1TenantNamesStateAndServiceMode) { return pgerror.Newf(pgcode.FeatureNotSupported, "cannot use tenant names") } } - 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.ServiceModeNone { + 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/tests/system_table_test.go b/pkg/sql/tests/system_table_test.go index 82fb2463f918..665c8f9117f6 100644 --- a/pkg/sql/tests/system_table_test.go +++ b/pkg/sql/tests/system_table_test.go @@ -52,7 +52,7 @@ func TestInitialKeys(t *testing.T) { var nonDescKeys int if systemTenant { codec = keys.SystemSQLCodec - nonDescKeys = 14 + nonDescKeys = 15 } else { codec = keys.MakeSQLCodec(roachpb.MustMakeTenantID(5)) nonDescKeys = 4 diff --git a/pkg/sql/tests/testdata/initial_keys b/pkg/sql/tests/testdata/initial_keys index 2533b85b3b00..9531aa06c4d2 100644 --- a/pkg/sql/tests/testdata/initial_keys +++ b/pkg/sql/tests/testdata/initial_keys @@ -1,6 +1,6 @@ initial-keys tenant=system ---- -100 keys: +101 keys: /System/"desc-idgen" /Table/3/1/1/2/1 /Table/3/1/3/2/1 @@ -56,6 +56,7 @@ initial-keys tenant=system /Table/7/1/0/0 /Table/8/1/1/0 /Table/8/2/"system"/0 + /Table/8/3/2/1/0 /NamespaceTable/30/1/0/0/"system"/4/1 /NamespaceTable/30/1/1/0/"public"/4/1 /NamespaceTable/30/1/1/29/"comments"/4/1 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", diff --git a/pkg/upgrade/upgrades/tenant_table_migration.go b/pkg/upgrade/upgrades/tenant_table_migration.go index 1844d86cf367..979c6b473d27 100644 --- a/pkg/upgrade/upgrades/tenant_table_migration.go +++ b/pkg/upgrade/upgrades/tenant_table_migration.go @@ -12,6 +12,7 @@ package upgrades import ( "context" + "strconv" "github.com/cockroachdb/cockroach/pkg/clusterversion" "github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb" @@ -22,55 +23,40 @@ import ( "github.com/cockroachdb/cockroach/pkg/upgrade" ) -const addTenantNameColumn = ` -ALTER TABLE system.public.tenants ADD COLUMN name STRING -AS (crdb_internal.pb_to_json('cockroach.sql.sqlbase.TenantInfo', info)->>'name') VIRTUAL` +// Note: the pre-existing column "active" becomes deprecated with the +// introduction of the new column data_state, but we cannot remove it +// right away because previous versions still depend on it. +const addTenantColumns = ` +ALTER TABLE system.public.tenants, + ALTER COLUMN active SET NOT VISIBLE, + ADD COLUMN name STRING, + ADD COLUMN data_state STRING, + ADD COLUMN service_mode STRING` -const addTenantNameIndex = ` -CREATE UNIQUE INDEX tenants_name_idx ON system.public.tenants (name ASC) +const addTenantIndexes = ` +CREATE UNIQUE INDEX tenants_name_idx ON system.public.tenants (name ASC); +CREATE INDEX tenants_service_mode_idx ON system.public.tenants (service_mode ASC); ` -const addSystemTenantEntry = ` -UPSERT INTO system.public.tenants (id, active, info) -VALUES (1, true, crdb_internal.json_to_pb('cockroach.sql.sqlbase.TenantInfo', '{"id":1,"state":0,"name":"` + catconstants.SystemTenantName + `"}')) -` - -func addTenantNameColumnAndSystemTenantEntry( +func extendTenantsTable( ctx context.Context, cs clusterversion.ClusterVersion, d upgrade.TenantDeps, ) error { - rows, err := d.InternalExecutor.QueryRowEx(ctx, "get-tenant-table-id", nil, - sessiondata.NodeUserSessionDataOverride, ` -SELECT n2.id - FROM system.public.namespace n1, - system.public.namespace n2 - WHERE n1.name = $1 - AND n1.id = n2."parentID" - AND n2.name = $2`, - catconstants.SystemDatabaseName, - catconstants.TenantsTableName, - ) - if err != nil { + tenantsTableID, err := getTenantsTableID(ctx, d) + if err != nil || tenantsTableID == 0 { return err } - if rows == nil { - // No system.tenants table. Nothing to do. - return nil - } - - // Retrieve the tenant table ID from the query above. - tenantsTableID := descpb.ID(int64(*rows[0].(*tree.DInt))) for _, op := range []operation{ { - name: "add-tenant-name-column", - schemaList: []string{"name"}, - query: addTenantNameColumn, + name: "add-tenant-columns", + schemaList: []string{"name", "data_state", "service_mode"}, + query: addTenantColumns, schemaExistsFn: hasColumn, }, { name: "make-tenant-name-unique", - schemaList: []string{"tenants_name_idx"}, - query: addTenantNameIndex, + schemaList: []string{"tenants_name_idx", "tenants_service_mode_idx"}, + query: addTenantIndexes, schemaExistsFn: hasIndex, }, } { @@ -80,6 +66,37 @@ SELECT n2.id } _, err = d.InternalExecutor.ExecEx(ctx, "add-system-entry", nil, - sessiondata.NodeUserSessionDataOverride, addSystemTenantEntry) + sessiondata.NodeUserSessionDataOverride, + `UPSERT INTO system.public.tenants (id, active, info, name, data_state, service_mode) +VALUES (1, true, + crdb_internal.json_to_pb('cockroach.sql.sqlbase.TenantInfo', '{}'), + '`+catconstants.SystemTenantName+`', + `+strconv.Itoa(int(descpb.DataStateReady))+`, + `+strconv.Itoa(int(descpb.ServiceModeShared))+`)`, + ) return err } + +func getTenantsTableID(ctx context.Context, d upgrade.TenantDeps) (descpb.ID, error) { + rows, err := d.InternalExecutor.QueryRowEx(ctx, "get-tenant-table-id", nil, + sessiondata.NodeUserSessionDataOverride, ` +SELECT n2.id + FROM system.public.namespace n1, + system.public.namespace n2 + WHERE n1.name = $1 + AND n1.id = n2."parentID" + AND n2.name = $2`, + catconstants.SystemDatabaseName, + catconstants.TenantsTableName, + ) + if err != nil { + return 0, err + } + if rows == nil { + // No system.tenants table. Nothing to do. + return 0, nil + } + tenantsTableID := descpb.ID(int64(*rows[0].(*tree.DInt))) + + return tenantsTableID, nil +} diff --git a/pkg/upgrade/upgrades/tenant_table_migration_test.go b/pkg/upgrade/upgrades/tenant_table_migration_test.go index 64d774803ca1..358760879a41 100644 --- a/pkg/upgrade/upgrades/tenant_table_migration_test.go +++ b/pkg/upgrade/upgrades/tenant_table_migration_test.go @@ -42,7 +42,7 @@ func TestUpdateTenantsTable(t *testing.T) { Server: &server.TestingKnobs{ DisableAutomaticVersionUpgrade: make(chan struct{}), BinaryVersionOverride: clusterversion.ByKey( - clusterversion.V23_1TenantNames - 1), + clusterversion.V23_1TenantNamesStateAndServiceMode - 1), }, }, }, @@ -60,7 +60,10 @@ func TestUpdateTenantsTable(t *testing.T) { var ( validationSchemas = []upgrades.Schema{ {Name: "name", ValidationFn: upgrades.HasColumn}, + {Name: "data_state", ValidationFn: upgrades.HasColumn}, + {Name: "service_mode", ValidationFn: upgrades.HasColumn}, {Name: "tenants_name_idx", ValidationFn: upgrades.HasIndex}, + {Name: "tenants_service_mode_idx", ValidationFn: upgrades.HasIndex}, } ) @@ -82,7 +85,7 @@ func TestUpdateTenantsTable(t *testing.T) { upgrades.Upgrade( t, sqlDB, - clusterversion.V23_1TenantNames, + clusterversion.V23_1TenantNamesStateAndServiceMode, nil, /* done */ false, /* expectError */ ) diff --git a/pkg/upgrade/upgrades/upgrades.go b/pkg/upgrade/upgrades/upgrades.go index 5b41e4c26b7a..4fbadbdc4020 100644 --- a/pkg/upgrade/upgrades/upgrades.go +++ b/pkg/upgrade/upgrades/upgrades.go @@ -197,10 +197,10 @@ var upgrades = []upgradebase.Upgrade{ upgrade.NoPrecondition, fixInvalidObjectsThatLookLikeBadUserfileConstraint, ), - upgrade.NewTenantUpgrade("add a name column to system.tenants and populate a system tenant entry", - toCV(clusterversion.V23_1TenantNames), + upgrade.NewTenantUpgrade("add columns to system.tenants and populate a system tenant entry", + toCV(clusterversion.V23_1TenantNamesStateAndServiceMode), upgrade.NoPrecondition, - addTenantNameColumnAndSystemTenantEntry, + extendTenantsTable, ), upgrade.NewTenantUpgrade("set the value or system.descriptor_id_seq for the system tenant", toCV(clusterversion.V23_1DescIDSequenceForSystemTenant),