8e6e53cf63
When running the test suite in CI (where requests are centralized from relatively few IPs), we'd occasionally hit Dockerhub's rate limits. Luckily Hashicorp runs a (limited) public mirror of the containers we need, so we can switch to them here in the tests. For consistency between developer and CI, we've opted to have the tests always pull from the Hashicorp mirror, rather than updating the CI runner to prefer the mirror. We exclude nomad and influxdb as we don't presently mirror these repos. Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com> Signed-off-by: Alexander Scheel <alex.scheel@hashicorp.com>
212 lines
5.8 KiB
Go
212 lines
5.8 KiB
Go
package cockroachdb
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"testing"
|
|
|
|
log "github.com/hashicorp/go-hclog"
|
|
"github.com/hashicorp/vault/helper/testhelpers/docker"
|
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
|
"github.com/hashicorp/vault/sdk/physical"
|
|
)
|
|
|
|
type Config struct {
|
|
docker.ServiceURL
|
|
TableName string
|
|
HATableName string
|
|
}
|
|
|
|
var _ docker.ServiceConfig = &Config{}
|
|
|
|
func prepareCockroachDBTestContainer(t *testing.T) (func(), *Config) {
|
|
if retURL := os.Getenv("CR_URL"); retURL != "" {
|
|
s, err := docker.NewServiceURLParse(retURL)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return func() {}, &Config{
|
|
ServiceURL: *s,
|
|
TableName: "vault." + defaultTableName,
|
|
HATableName: "vault." + defaultHATableName,
|
|
}
|
|
}
|
|
|
|
runner, err := docker.NewServiceRunner(docker.RunOptions{
|
|
ImageRepo: "docker.mirror.hashicorp.services/cockroachdb/cockroach",
|
|
ImageTag: "release-1.0",
|
|
ContainerName: "cockroachdb",
|
|
Cmd: []string{"start", "--insecure"},
|
|
Ports: []string{"26257/tcp"},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Could not start docker CockroachDB: %s", err)
|
|
}
|
|
svc, err := runner.StartService(context.Background(), connectCockroachDB)
|
|
if err != nil {
|
|
t.Fatalf("Could not start docker CockroachDB: %s", err)
|
|
}
|
|
|
|
return svc.Cleanup, svc.Config.(*Config)
|
|
}
|
|
|
|
func connectCockroachDB(ctx context.Context, host string, port int) (docker.ServiceConfig, error) {
|
|
u := url.URL{
|
|
Scheme: "postgresql",
|
|
User: url.UserPassword("root", ""),
|
|
Host: fmt.Sprintf("%s:%d", host, port),
|
|
RawQuery: "sslmode=disable",
|
|
}
|
|
|
|
db, err := sql.Open("pgx", u.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer db.Close()
|
|
|
|
database := "vault"
|
|
_, err = db.Exec(fmt.Sprintf("CREATE DATABASE %s", database))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Config{
|
|
ServiceURL: *docker.NewServiceURL(u),
|
|
TableName: database + "." + defaultTableName,
|
|
HATableName: database + "." + defaultHATableName,
|
|
}, nil
|
|
}
|
|
|
|
func TestCockroachDBBackend(t *testing.T) {
|
|
cleanup, config := prepareCockroachDBTestContainer(t)
|
|
defer cleanup()
|
|
|
|
hae := os.Getenv("CR_HA_ENABLED")
|
|
if hae == "" {
|
|
hae = "true"
|
|
}
|
|
|
|
// Run vault tests
|
|
logger := logging.NewVaultLogger(log.Debug)
|
|
|
|
b1, err := NewCockroachDBBackend(map[string]string{
|
|
"connection_url": config.URL().String(),
|
|
"table": config.TableName,
|
|
"ha_table": config.HATableName,
|
|
"ha_enabled": hae,
|
|
}, logger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create new backend: %v", err)
|
|
}
|
|
|
|
b2, err := NewCockroachDBBackend(map[string]string{
|
|
"connection_url": config.URL().String(),
|
|
"table": config.TableName,
|
|
"ha_table": config.HATableName,
|
|
"ha_enabled": hae,
|
|
}, logger)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create new backend: %v", err)
|
|
}
|
|
|
|
defer func() {
|
|
truncate(t, b1)
|
|
truncate(t, b2)
|
|
}()
|
|
|
|
physical.ExerciseBackend(t, b1)
|
|
truncate(t, b1)
|
|
physical.ExerciseBackend_ListPrefix(t, b1)
|
|
truncate(t, b1)
|
|
physical.ExerciseTransactionalBackend(t, b1)
|
|
truncate(t, b1)
|
|
|
|
ha1, ok1 := b1.(physical.HABackend)
|
|
ha2, ok2 := b2.(physical.HABackend)
|
|
if !ok1 || !ok2 {
|
|
t.Fatalf("CockroachDB does not implement HABackend")
|
|
}
|
|
|
|
if ha1.HAEnabled() && ha2.HAEnabled() {
|
|
logger.Info("Running ha backend tests")
|
|
physical.ExerciseHABackend(t, ha1, ha2)
|
|
}
|
|
}
|
|
|
|
func truncate(t *testing.T, b physical.Backend) {
|
|
crdb := b.(*CockroachDBBackend)
|
|
_, err := crdb.client.Exec("TRUNCATE TABLE " + crdb.table)
|
|
if err != nil {
|
|
t.Fatalf("Failed to drop table: %v", err)
|
|
}
|
|
if crdb.haEnabled {
|
|
_, err = crdb.client.Exec("TRUNCATE TABLE " + crdb.haTable)
|
|
if err != nil {
|
|
t.Fatalf("Failed to drop table: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateDBTable(t *testing.T) {
|
|
type testCase struct {
|
|
table string
|
|
expectErr bool
|
|
}
|
|
|
|
tests := map[string]testCase{
|
|
"first character is letter": {"abcdef", false},
|
|
"first character is underscore": {"_bcdef", false},
|
|
"exclamation point": {"ab!def", true},
|
|
"at symbol": {"ab@def", true},
|
|
"hash": {"ab#def", true},
|
|
"percent": {"ab%def", true},
|
|
"carrot": {"ab^def", true},
|
|
"ampersand": {"ab&def", true},
|
|
"star": {"ab*def", true},
|
|
"left paren": {"ab(def", true},
|
|
"right paren": {"ab)def", true},
|
|
"dash": {"ab-def", true},
|
|
"digit": {"a123ef", false},
|
|
"dollar end": {"abcde$", false},
|
|
"dollar middle": {"ab$def", false},
|
|
"dollar start": {"$bcdef", true},
|
|
"backtick prefix": {"`bcdef", true},
|
|
"backtick middle": {"ab`def", true},
|
|
"backtick suffix": {"abcde`", true},
|
|
"single quote prefix": {"'bcdef", true},
|
|
"single quote middle": {"ab'def", true},
|
|
"single quote suffix": {"abcde'", true},
|
|
"double quote prefix": {`"bcdef`, true},
|
|
"double quote middle": {`ab"def`, true},
|
|
"double quote suffix": {`abcde"`, true},
|
|
"underscore with all runes": {"_bcd123__a__$", false},
|
|
"all runes": {"abcd123__a__$", false},
|
|
"default table name": {defaultTableName, false},
|
|
}
|
|
|
|
for name, test := range tests {
|
|
t.Run(name, func(t *testing.T) {
|
|
err := validateDBTable(test.table)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
})
|
|
t.Run(fmt.Sprintf("database: %s", name), func(t *testing.T) {
|
|
dbTable := fmt.Sprintf("%s.%s", test.table, test.table)
|
|
err := validateDBTable(dbTable)
|
|
if test.expectErr && err == nil {
|
|
t.Fatalf("err expected, got nil")
|
|
}
|
|
if !test.expectErr && err != nil {
|
|
t.Fatalf("no error expected, got: %s", err)
|
|
}
|
|
})
|
|
}
|
|
}
|