Skip to content

Commit

Permalink
tenantcapabilities: introduce a decoder for tenant capabilities
Browse files Browse the repository at this point in the history
This patch introduces a decoder, which takes raw KVs from the
system.tenants table and decodes them into
(TenantID, TenantCapability) pairs.

This is currently unusued. In a future commit, we'll establish a
rangefeed over the `system.tenants` table to maintain a cached view
of the global capabilities state. This decoder will be used to parse the
rangefeed output. Looking towards such a watcher component, the decoder
is placed in the `tenantcapabilitieswatcher` package.

References cockroachdb#94643

Epic: CRDB-18503

Release note: None
  • Loading branch information
arulajmani committed Jan 6, 2023
1 parent 50eb8d2 commit a57cf17
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pkg/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ ALL_TESTS = [
"//pkg/kv/kvserver/uncertainty:uncertainty_test",
"//pkg/kv/kvserver:kvserver_test",
"//pkg/kv:kv_test",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitieswatcher:tenantcapabilitieswatcher_test",
"//pkg/obsservice/obslib/ingest:ingest_test",
"//pkg/roachpb:roachpb_disallowed_imports_test",
"//pkg/roachpb:roachpb_test",
Expand Down Expand Up @@ -1262,6 +1263,9 @@ GO_TARGETS = [
"//pkg/multitenant/multitenantcpu:multitenantcpu",
"//pkg/multitenant/multitenantio:multitenantio",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:tenantcapabilitiespb",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitieswatcher:tenantcapabilitieswatcher",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitieswatcher:tenantcapabilitieswatcher_test",
"//pkg/multitenant/tenantcapabilities:tenantcapabilities",
"//pkg/multitenant/tenantcostmodel:tenantcostmodel",
"//pkg/multitenant:multitenant",
"//pkg/obs:obs",
Expand Down Expand Up @@ -2593,7 +2597,9 @@ GET_X_DATA_TARGETS = [
"//pkg/multitenant:get_x_data",
"//pkg/multitenant/multitenantcpu:get_x_data",
"//pkg/multitenant/multitenantio:get_x_data",
"//pkg/multitenant/tenantcapabilities:get_x_data",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb:get_x_data",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitieswatcher:get_x_data",
"//pkg/multitenant/tenantcostmodel:get_x_data",
"//pkg/obs:get_x_data",
"//pkg/obsservice/cmd/obsservice:get_x_data",
Expand Down
14 changes: 14 additions & 0 deletions pkg/multitenant/tenantcapabilities/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data")

go_library(
name = "tenantcapabilities",
srcs = ["capabilities.go"],
importpath = "github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities",
visibility = ["//visibility:public"],
deps = [
"//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb",
"//pkg/roachpb",
],
)

get_x_data(name = "get_x_data")
22 changes: 22 additions & 0 deletions pkg/multitenant/tenantcapabilities/capabilities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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 tenantcapabilities

import (
"github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb"
"github.com/cockroachdb/cockroach/pkg/roachpb"
)

// CapabilitiesEntry ties together a tenantID with its capabilities.
type CapabilitiesEntry struct {
TenantID roachpb.TenantID
TenantCapabilities tenantcapabilitiespb.TenantCapabilities
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
load("//build/bazelutil/unused_checker:unused.bzl", "get_x_data")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "tenantcapabilitieswatcher",
srcs = ["decoder.go"],
importpath = "github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitieswatcher",
visibility = ["//visibility:public"],
deps = [
"//pkg/keys",
"//pkg/multitenant/tenantcapabilities",
"//pkg/roachpb",
"//pkg/sql/catalog",
"//pkg/sql/catalog/descpb",
"//pkg/sql/catalog/systemschema",
"//pkg/sql/rowenc",
"//pkg/sql/rowenc/valueside",
"//pkg/sql/sem/tree",
"//pkg/sql/types",
"//pkg/util/protoutil",
"@com_github_cockroachdb_errors//:errors",
],
)

go_test(
name = "tenantcapabilitieswatcher_test",
srcs = [
"decoder_test.go",
"main_test.go",
],
args = ["-test.timeout=295s"],
embed = [":tenantcapabilitieswatcher"],
deps = [
"//pkg/base",
"//pkg/keys",
"//pkg/multitenant/tenantcapabilities/tenantcapabilitiespb",
"//pkg/roachpb",
"//pkg/security/securityassets",
"//pkg/security/securitytest",
"//pkg/server",
"//pkg/sql/catalog/descpb",
"//pkg/testutils/serverutils",
"//pkg/testutils/sqlutils",
"//pkg/testutils/testcluster",
"//pkg/util/leaktest",
"//pkg/util/protoutil",
"@com_github_stretchr_testify//require",
],
)

get_x_data(name = "get_x_data")
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// 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 tenantcapabilitieswatcher

import (
"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/systemschema"
"github.com/cockroachdb/cockroach/pkg/sql/rowenc"
"github.com/cockroachdb/cockroach/pkg/sql/rowenc/valueside"
"github.com/cockroachdb/cockroach/pkg/sql/sem/tree"
"github.com/cockroachdb/cockroach/pkg/sql/types"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
"github.com/cockroachdb/errors"
)

// decoder decodes rows from system.tenants. It's not
// safe for concurrent use.
type decoder struct {
alloc tree.DatumAlloc
columns []catalog.Column
decoder valueside.Decoder
}

// newDecoder constructs and returns a decoder.
func newDecoder() *decoder {
columns := systemschema.TenantsTable.PublicColumns()
return &decoder{
columns: columns,
decoder: valueside.MakeDecoder(columns),
}
}

func (d *decoder) decode(kv roachpb.KeyValue) (tenantcapabilities.CapabilitiesEntry, error) {
// First we decode the tenantID from the key.
var tenID roachpb.TenantID
types := []*types.T{d.columns[0].GetType()}
tenantIDRow := make([]rowenc.EncDatum, 1)
_, _, err := rowenc.DecodeIndexKey(keys.SystemSQLCodec, types, tenantIDRow, nil /* colDirs */, kv.Key)
if err != nil {
return tenantcapabilities.CapabilitiesEntry{}, err
}
if err := tenantIDRow[0].EnsureDecoded(types[0], &d.alloc); err != nil {
return tenantcapabilities.CapabilitiesEntry{},
errors.NewAssertionErrorWithWrappedErrf(err, "failed to decode key in system.tenants %v", kv.Key)
}
tenID, err = roachpb.MakeTenantID(uint64(tree.MustBeDInt(tenantIDRow[0].Datum)))
if err != nil {
return tenantcapabilities.CapabilitiesEntry{}, err
}

// The remaining columns are stored in the value; we're just interested in the
// info column.
if !kv.Value.IsPresent() {
return tenantcapabilities.CapabilitiesEntry{},
errors.AssertionFailedf("missing value for tenant: %v", tenID)
}

bytes, err := kv.Value.GetTuple()
if err != nil {
return tenantcapabilities.CapabilitiesEntry{}, err
}
datums, err := d.decoder.Decode(&d.alloc, bytes)
if err != nil {
return tenantcapabilities.CapabilitiesEntry{}, err
}

var tenantInfo descpb.TenantInfo
if i := datums[2]; i != tree.DNull {
infoBytes := tree.MustBeDBytes(i)
if err := protoutil.Unmarshal([]byte(infoBytes), &tenantInfo); err != nil {
return tenantcapabilities.CapabilitiesEntry{}, errors.Wrapf(err, "failed to unmarshall tenant info")
}
}

return tenantcapabilities.CapabilitiesEntry{
TenantID: tenID,
TenantCapabilities: tenantInfo.Capabilities,
}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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 tenantcapabilitieswatcher

import (
"context"
"fmt"
"testing"

"github.com/cockroachdb/cockroach/pkg/base"
"github.com/cockroachdb/cockroach/pkg/keys"
"github.com/cockroachdb/cockroach/pkg/multitenant/tenantcapabilities/tenantcapabilitiespb"
"github.com/cockroachdb/cockroach/pkg/roachpb"
"github.com/cockroachdb/cockroach/pkg/sql/catalog/descpb"
"github.com/cockroachdb/cockroach/pkg/testutils/sqlutils"
"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
"github.com/cockroachdb/cockroach/pkg/util/leaktest"
"github.com/cockroachdb/cockroach/pkg/util/protoutil"
"github.com/stretchr/testify/require"
)

// TestDecodeCapabilities verifies that we can correctly decode
// TenantCapabilities stored in the system.tenants table.
func TestDecodeCapabilities(t *testing.T) {
defer leaktest.AfterTest(t)()

ctx := context.Background()
tc := testcluster.StartTestCluster(t, 1, base.TestClusterArgs{
ServerArgs: base.TestServerArgs{
DisableDefaultTestTenant: true, // system.tenants only exists for the system tenant
},
})
defer tc.Stopper().Stop(ctx)

const dummyTableName = "dummy_system_tenants"
tdb := sqlutils.MakeSQLRunner(tc.ServerConn(0))
tdb.Exec(t, fmt.Sprintf("CREATE TABLE %s (LIKE system.tenants INCLUDING ALL)", dummyTableName))

var dummyTableID uint32
tdb.QueryRow(t, fmt.Sprintf(
`SELECT table_id FROM crdb_internal.tables WHERE name = '%s'`, dummyTableName),
).Scan(&dummyTableID)

tenantID, err := roachpb.MakeTenantID(10)
require.NoError(t, err)
info := descpb.TenantInfo{
Capabilities: tenantcapabilitiespb.TenantCapabilities{
CanAdminSplit: true,
},
}
buf, err := protoutil.Marshal(&info)
require.NoError(t, err)
tdb.Exec(
t,
fmt.Sprintf("INSERT INTO %s (id, active, info) VALUES ($1, $2, $3)", dummyTableName),
tenantID.ToUint64(), true /* active */, buf,
)

// Read the row .
k := keys.SystemSQLCodec.IndexPrefix(dummyTableID, keys.TenantsTablePrimaryKeyIndexID)
rows, err := tc.Server(0).DB().Scan(ctx, k, k.PrefixEnd(), 0 /* maxRows */)
require.NoError(t, err)
require.Len(t, rows, 1)

// Decode and verify.
row := rows[0]
decoder := newDecoder()
got, err := decoder.decode(roachpb.KeyValue{
Key: row.Key,
Value: *row.Value,
})
require.NoError(t, err)

require.Equal(t, tenantID, got.TenantID)
require.Equal(t, info.Capabilities, got.TenantCapabilities)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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 tenantcapabilitieswatcher

import (
"os"
"testing"

"github.com/cockroachdb/cockroach/pkg/security/securityassets"
"github.com/cockroachdb/cockroach/pkg/security/securitytest"
"github.com/cockroachdb/cockroach/pkg/server"
"github.com/cockroachdb/cockroach/pkg/testutils/serverutils"
"github.com/cockroachdb/cockroach/pkg/testutils/testcluster"
)

func TestMain(m *testing.M) {
securityassets.SetLoader(securitytest.EmbeddedAssets)
serverutils.InitTestServerFactory(server.TestServerFactory)
serverutils.InitTestClusterFactory(testcluster.TestClusterFactory)
os.Exit(m.Run())
}

//go:generate ../../../util/leaktest/add-leaktest.sh *_test.go

0 comments on commit a57cf17

Please sign in to comment.