Skip to content

Commit

Permalink
Add cockroachdb quota manager driver
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Antonio Osorio <[email protected]>
  • Loading branch information
JAORMX committed Nov 15, 2022
1 parent ca051d2 commit 69e58af
Show file tree
Hide file tree
Showing 7 changed files with 465 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ jobs:
cache: true

- name: Run tests
run: go test -v ./storage/crdb/...
run: go test -v ./storage/crdb/... ./quota/crdbqm/...
3 changes: 2 additions & 1 deletion cmd/trillian_log_server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ import (
_ "github.com/google/trillian/storage/crdb"
_ "github.com/google/trillian/storage/mysql"

// Load MySQL quota provider
// Load quota providers
_ "github.com/google/trillian/quota/crdbqm"
_ "github.com/google/trillian/quota/mysqlqm"
)

Expand Down
3 changes: 2 additions & 1 deletion cmd/trillian_log_signer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ import (
_ "github.com/google/trillian/storage/crdb"
_ "github.com/google/trillian/storage/mysql"

// Load MySQL quota provider
// Load quota providers
_ "github.com/google/trillian/quota/crdbqm"
_ "github.com/google/trillian/quota/mysqlqm"
)

Expand Down
50 changes: 50 additions & 0 deletions quota/crdbqm/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2022 Trillian Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package crdbqm

import (
"os"
"testing"

"github.com/cockroachdb/cockroach-go/v2/testserver"
"github.com/google/trillian/storage/testdb"
"k8s.io/klog/v2"
)

func TestMain(m *testing.M) {
ts, err := testserver.NewTestServer()
if err != nil {
klog.Errorf("Failed to start test server: %v", err)
os.Exit(1)
}
defer ts.Stop()

// reset the test server URL path. By default cockroach sets it
// to point to a default database, we don't want that.
dburl := ts.PGURL()
dburl.Path = "/"

// Set the environment variable for the test server
os.Setenv(testdb.CockroachDBURIEnv, dburl.String())

if !testdb.CockroachDBAvailable() {
klog.Errorf("CockroachDB not available, skipping all CockroachDB storage tests")
return
}

status := m.Run()

os.Exit(status)
}
100 changes: 100 additions & 0 deletions quota/crdbqm/crdb_quota.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2022 Trillian Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package crdbqm defines a CockroachDB-based quota.Manager implementation.
package crdbqm

import (
"context"
"database/sql"
"errors"

"github.com/google/trillian/quota"
)

const (
// DefaultMaxUnsequenced is a suggested value for MaxUnsequencedRows.
// Note that this is a Global/Write quota suggestion, so it applies across trees.
DefaultMaxUnsequenced = 500000 // About 2h of non-stop signing at 70QPS.

// TODO(jaosorior): Come up with a more optimal solution for CRDB, as this is
// linear and too costly.
countFromUnsequencedTable = "SELECT COUNT(*) FROM Unsequenced"
)

// ErrTooManyUnsequencedRows is returned when tokens are requested but Unsequenced has grown
// beyond the configured limit.
var ErrTooManyUnsequencedRows = errors.New("too many unsequenced rows")

// QuotaManager is a CockroachDB-based quota.Manager implementation.
//
// QuotaManager only implements Global/Write quotas, which is based on the number of Unsequenced
// rows (to be exact, tokens = MaxUnsequencedRows - actualUnsequencedRows).
// Other quotas are considered infinite.
type QuotaManager struct {
DB *sql.DB
MaxUnsequencedRows int
}

// GetTokens implements quota.Manager.GetTokens.
// It doesn't actually reserve or retrieve tokens, instead it allows access based on the number of
// rows in the Unsequenced table.
func (m *QuotaManager) GetTokens(ctx context.Context, numTokens int, specs []quota.Spec) error {
for _, spec := range specs {
if spec.Group != quota.Global || spec.Kind != quota.Write {
continue
}
// Only allow global writes if Unsequenced is under the expected limit
count, err := m.countUnsequenced(ctx)
if err != nil {
return err
}
if count+numTokens > m.MaxUnsequencedRows {
return ErrTooManyUnsequencedRows
}
}
return nil
}

// PutTokens implements quota.Manager.PutTokens.
// It's a noop for QuotaManager.
func (m *QuotaManager) PutTokens(ctx context.Context, numTokens int, specs []quota.Spec) error {
return nil
}

// ResetQuota implements quota.Manager.ResetQuota.
// It's a noop for QuotaManager.
func (m *QuotaManager) ResetQuota(ctx context.Context, specs []quota.Spec) error {
return nil
}

func (m *QuotaManager) countUnsequenced(ctx context.Context) (int, error) {
// table names are lowercase for some reason
rows, err := m.DB.QueryContext(ctx, countFromUnsequencedTable)
if err != nil {
return 0, err
}
defer rows.Close()
if !rows.Next() {
return 0, errors.New("cursor has no rows after quota limit determination query")
}
var count int
if err := rows.Scan(&count); err != nil {
return 0, err
}
if rows.Next() {
return 0, errors.New("too many rows returned from quota limit determination query")
}
return count, nil
}
Loading

0 comments on commit 69e58af

Please sign in to comment.