github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/tenant.go (about) 1 // Copyright 2020 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package sql 12 13 import ( 14 "context" 15 16 "github.com/cockroachdb/cockroach/pkg/keys" 17 "github.com/cockroachdb/cockroach/pkg/roachpb" 18 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgcode" 19 "github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror" 20 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 21 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 22 "github.com/cockroachdb/cockroach/pkg/util/log" 23 "github.com/cockroachdb/errors" 24 ) 25 26 // rejectIfCantCoordinateMultiTenancy returns an error if the current tenant is 27 // disallowed from coordinating tenant management operations on behalf of a 28 // multi-tenant cluster. Only the system tenant has permissions to do so. 29 func rejectIfCantCoordinateMultiTenancy(codec keys.SQLCodec, op string) error { 30 // NOTE: even if we got this wrong, the rest of the function would fail for 31 // a non-system tenant because they would be missing a system.tenant table. 32 if !codec.ForSystemTenant() { 33 return pgerror.Newf(pgcode.InsufficientPrivilege, 34 "only the system tenant can %s other tenants", op) 35 } 36 return nil 37 } 38 39 // rejectIfSystemTenant returns an error if the provided tenant ID is the system 40 // tenant's ID. 41 func rejectIfSystemTenant(tenID uint64, op string) error { 42 if roachpb.IsSystemTenantID(tenID) { 43 return pgerror.Newf(pgcode.InvalidParameterValue, 44 "cannot %s tenant \"%d\", ID assigned to system tenant", op, tenID) 45 } 46 return nil 47 } 48 49 // CreateTenant implements the tree.TenantOperator interface. 50 func (p *planner) CreateTenant(ctx context.Context, tenID uint64, tenInfo []byte) error { 51 const op = "create" 52 if err := rejectIfCantCoordinateMultiTenancy(p.ExecCfg().Codec, op); err != nil { 53 return err 54 } 55 if err := rejectIfSystemTenant(tenID, op); err != nil { 56 return err 57 } 58 59 // NB: interface{}([]byte(nil)) != interface{}(nil). 60 var tenInfoArg interface{} 61 if tenInfo != nil { 62 tenInfoArg = tenInfo 63 } 64 65 // Insert into the tenant table and detect collisions. 66 if num, err := p.ExecCfg().InternalExecutor.ExecEx( 67 ctx, "create-tenant", p.Txn(), sqlbase.NodeUserSessionDataOverride, 68 `INSERT INTO system.tenants (id, info) VALUES ($1, $2)`, tenID, tenInfoArg, 69 ); err != nil { 70 if pgerror.GetPGCode(err) == pgcode.UniqueViolation { 71 return pgerror.Newf(pgcode.DuplicateObject, "tenant \"%d\" already exists", tenID) 72 } 73 return errors.Wrap(err, "inserting new tenant") 74 } else if num != 1 { 75 log.Fatalf(ctx, "unexpected number of rows affected: %d", num) 76 } 77 78 // Initialize the tenant's keyspace. 79 schema := sqlbase.MakeMetadataSchema( 80 keys.MakeSQLCodec(roachpb.MakeTenantID(tenID)), 81 nil, /* defaultZoneConfig */ 82 nil, /* defaultZoneConfig */ 83 ) 84 kvs, splits := schema.GetInitialValues() 85 b := p.Txn().NewBatch() 86 for _, kv := range kvs { 87 b.CPut(kv.Key, &kv.Value, nil) 88 } 89 if err := p.Txn().Run(ctx, b); err != nil { 90 if errors.HasType(err, (*roachpb.ConditionFailedError)(nil)) { 91 return errors.Wrap(err, "programming error: "+ 92 "tenant already exists but was not in system.tenants table") 93 } 94 return err 95 } 96 97 // TODO(nvanbenschoten): we currently neither split ranges between a single 98 // tenant's tables nor split ranges between different tenant keyspaces. We 99 // should do both. Performing the splits here won't have the desired effect 100 // until we also teach SystemConfig.ComputeSplitKey about tenant tables and 101 // tenant boundaries. Tracked in #48774. 102 _ = splits 103 104 // Tenant creation complete! Note that sqlmigrations have not been run yet. 105 // They will be run when a sqlServer bound to this tenant is first launched. 106 107 return nil 108 } 109 110 // DestroyTenant implements the tree.TenantOperator interface. 111 func (p *planner) DestroyTenant(ctx context.Context, tenID uint64) error { 112 const op = "destroy" 113 if err := rejectIfCantCoordinateMultiTenancy(p.ExecCfg().Codec, op); err != nil { 114 return err 115 } 116 if err := rejectIfSystemTenant(tenID, op); err != nil { 117 return err 118 } 119 120 // Query the tenant's active status. If it is marked as inactive, it is 121 // already destroyed. 122 if row, err := p.ExecCfg().InternalExecutor.QueryRowEx( 123 ctx, "destroy-tenant", p.Txn(), sqlbase.NodeUserSessionDataOverride, 124 `SELECT active FROM system.tenants WHERE id = $1`, tenID, 125 ); err != nil { 126 return errors.Wrap(err, "deleting tenant") 127 } else if row == nil { 128 return pgerror.Newf(pgcode.UndefinedObject, "tenant \"%d\" does not exist", tenID) 129 } else if !bool(tree.MustBeDBool(row[0])) { 130 return nil // tenant already destroyed 131 } 132 133 // Mark the tenant as inactive. 134 if num, err := p.ExecCfg().InternalExecutor.ExecEx( 135 ctx, "destroy-tenant", p.Txn(), sqlbase.NodeUserSessionDataOverride, 136 `UPDATE system.tenants SET active = false WHERE id = $1`, tenID, 137 ); err != nil { 138 return errors.Wrap(err, "deleting tenant") 139 } else if num != 1 { 140 log.Fatalf(ctx, "unexpected number of rows affected: %d", num) 141 } 142 143 // TODO(nvanbenschoten): actually clear tenant keyspace. We don't want to do 144 // this synchronously in the same transaction, because we could be deleting 145 // a very large amount of data. Tracked in #48775. 146 147 return nil 148 }