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  }