From a77b71abcf13fe8935c42e4732c7646a41986e2a Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Wed, 10 May 2017 22:46:00 -0400 Subject: [PATCH 01/10] Adding cockroachdb physical backend --- physical/cockroachdb.go | 145 +++++++++++++++++++++++++++++++++++ physical/cockroachdb_test.go | 47 ++++++++++++ physical/physical.go | 1 + 3 files changed, 193 insertions(+) create mode 100644 physical/cockroachdb.go create mode 100644 physical/cockroachdb_test.go diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go new file mode 100644 index 000000000000..b797f7191803 --- /dev/null +++ b/physical/cockroachdb.go @@ -0,0 +1,145 @@ +package physical + +import ( + "database/sql" + "fmt" + "sort" + "strings" + "time" + + log "github.com/mgutz/logxi/v1" + + "github.com/armon/go-metrics" + "github.com/lib/pq" +) + +// CockroachDBBackend Backend is a physical backend that stores data +// within a CockroachDB database. +type CockroachDBBackend struct { + table string + client *sql.DB + put_query string + get_query string + delete_query string + list_query string + logger log.Logger +} + +// newCockroachDBBackend constructs a CockroachDB backend using the given +// API client, server address, credentials, and database. +func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, error) { + // Get the CockroachDB credentials to perform read/write operations. + connURL, ok := conf["connection_url"] + if !ok || connURL == "" { + return nil, fmt.Errorf("missing connection_url") + } + + unquotedTable, ok := conf["table"] + if !ok { + unquotedTable = "vault_kv_store" + } + quotedTable := pq.QuoteIdentifier(unquotedTable) + + // Create CockroachDB handle for the database. + db, err := sql.Open("postgres", connURL) + if err != nil { + return nil, fmt.Errorf("failed to connect to cockroachdb: %v", err) + } + + // Create the required table if it doesn't exists. + create_query := "CREATE TABLE IF NOT EXISTS " + unquotedTable + + " (path STRING, value BYTES, PRIMARY KEY (path))" + if _, err := db.Exec(create_query); err != nil { + return nil, fmt.Errorf("failed to create mysql table: %v", err) + } + + // Setup the backend. + m := &CockroachDBBackend{ + table: quotedTable, + client: db, + put_query: "INSERT INTO " + unquotedTable + " VALUES($1, $2)" + + " ON CONFLICT (path) DO " + + " UPDATE SET (path, value) = ($1, $2)", + get_query: "SELECT value FROM " + unquotedTable + " WHERE path = $1", + delete_query: "DELETE FROM " + unquotedTable + " WHERE path = $1", + list_query: "SELECT path FROM " + unquotedTable + " WHERE path LIKE concat($1, '%')", + logger: logger, + } + + return m, nil +} + +// Put is used to insert or update an entry. +func (m *CockroachDBBackend) Put(entry *Entry) error { + defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) + + _, err := m.client.Exec(m.put_query, entry.Key, entry.Value) + if err != nil { + return err + } + return nil +} + +// Get is used to fetch and entry. +func (m *CockroachDBBackend) Get(fullPath string) (*Entry, error) { + defer metrics.MeasureSince([]string{"cockroachdb", "get"}, time.Now()) + + var result []byte + err := m.client.QueryRow(m.get_query, fullPath).Scan(&result) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + + ent := &Entry{ + Key: fullPath, + Value: result, + } + return ent, nil +} + +// Delete is used to permanently delete an entry +func (m *CockroachDBBackend) Delete(fullPath string) error { + defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) + + _, err := m.client.Exec(m.delete_query, fullPath) + if err != nil { + return err + } + return nil +} + +// List is used to list all the keys under a given +// prefix, up to the next prefix. +func (m *CockroachDBBackend) List(prefix string) ([]string, error) { + defer metrics.MeasureSince([]string{"cockroachdb", "list"}, time.Now()) + + rows, err := m.client.Query(m.list_query, prefix) + if err != nil { + return nil, err + } + defer rows.Close() + + var keys []string + for rows.Next() { + var key string + err = rows.Scan(&key) + if err != nil { + return nil, fmt.Errorf("failed to scan rows: %v", err) + } + + key = strings.TrimPrefix(key, prefix) + if i := strings.Index(key, "/"); i == -1 { + // Add objects only from the current 'folder' + keys = append(keys, key) + } else if i != -1 { + // Add truncated 'folder' paths + keys = appendIfMissing(keys, string(key[:i+1])) + } + } + + sort.Strings(keys) + return keys, nil +} diff --git a/physical/cockroachdb_test.go b/physical/cockroachdb_test.go new file mode 100644 index 000000000000..d98611168d2b --- /dev/null +++ b/physical/cockroachdb_test.go @@ -0,0 +1,47 @@ +package physical + +import ( + "os" + "testing" + + "github.com/hashicorp/vault/helper/logformat" + log "github.com/mgutz/logxi/v1" + + _ "github.com/lib/pq" +) + +func TestCockroachDBBackend(t *testing.T) { + connURL := os.Getenv("CRURL") + if connURL == "" { + t.SkipNow() + } + + table := os.Getenv("CRTABLE") + if table == "" { + table = "vault_kv_store" + } + + // Run vault tests + logger := logformat.NewVaultLogger(log.LevelTrace) + + b, err := NewBackend("cockroachdb", logger, map[string]string{ + "connection_url": connURL, + "table": table, + }) + + if err != nil { + t.Fatalf("Failed to create new backend: %v", err) + } + + defer func() { + pg := b.(*CockroachDBBackend) + _, err := pg.client.Exec("TRUNCATE TABLE " + pg.table) + if err != nil { + t.Fatalf("Failed to drop table: %v", err) + } + }() + + testBackend(t, b) + testBackend_ListPrefix(t, b) + +} diff --git a/physical/physical.go b/physical/physical.go index b35d281ceba0..7b09b60f97e3 100644 --- a/physical/physical.go +++ b/physical/physical.go @@ -151,6 +151,7 @@ var builtinBackends = map[string]Factory{ "mssql": newMsSQLBackend, "mysql": newMySQLBackend, "postgresql": newPostgreSQLBackend, + "cockroachdb": newCockroachDBBackend, "swift": newSwiftBackend, "gcs": newGCSBackend, } From f5b4dadeab240f8f5b1ecf9ad9ce23d83126ab72 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Thu, 11 May 2017 09:30:35 -0400 Subject: [PATCH 02/10] cleanup --- physical/cockroachdb.go | 73 ++++++++++++++++++++++-------------- physical/cockroachdb_test.go | 4 +- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index b797f7191803..aa6e097a6140 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -10,19 +10,15 @@ import ( log "github.com/mgutz/logxi/v1" "github.com/armon/go-metrics" - "github.com/lib/pq" ) // CockroachDBBackend Backend is a physical backend that stores data // within a CockroachDB database. type CockroachDBBackend struct { - table string - client *sql.DB - put_query string - get_query string - delete_query string - list_query string - logger log.Logger + table string + client *sql.DB + statements map[string]*sql.Stmt + logger log.Logger } // newCockroachDBBackend constructs a CockroachDB backend using the given @@ -34,11 +30,10 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, return nil, fmt.Errorf("missing connection_url") } - unquotedTable, ok := conf["table"] + dbTable, ok := conf["table"] if !ok { - unquotedTable = "vault_kv_store" + dbTable = "vault_kv_store" } - quotedTable := pq.QuoteIdentifier(unquotedTable) // Create CockroachDB handle for the database. db, err := sql.Open("postgres", connURL) @@ -47,33 +42,52 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, } // Create the required table if it doesn't exists. - create_query := "CREATE TABLE IF NOT EXISTS " + unquotedTable + + createQuery := "CREATE TABLE IF NOT EXISTS " + dbTable + " (path STRING, value BYTES, PRIMARY KEY (path))" - if _, err := db.Exec(create_query); err != nil { + if _, err := db.Exec(createQuery); err != nil { return nil, fmt.Errorf("failed to create mysql table: %v", err) } - // Setup the backend. + // Setup the backend m := &CockroachDBBackend{ - table: quotedTable, - client: db, - put_query: "INSERT INTO " + unquotedTable + " VALUES($1, $2)" + + table: dbTable, + client: db, + statements: make(map[string]*sql.Stmt), + logger: logger, + } + + // Prepare all the statements required + statements := map[string]string{ + "put": "INSERT INTO " + dbTable + " VALUES($1, $2)" + " ON CONFLICT (path) DO " + " UPDATE SET (path, value) = ($1, $2)", - get_query: "SELECT value FROM " + unquotedTable + " WHERE path = $1", - delete_query: "DELETE FROM " + unquotedTable + " WHERE path = $1", - list_query: "SELECT path FROM " + unquotedTable + " WHERE path LIKE concat($1, '%')", - logger: logger, + "get": "SELECT value FROM " + dbTable + " WHERE path = $1", + "delete": "DELETE FROM " + dbTable + " WHERE path = $1", + "list": "SELECT path FROM " + dbTable + " WHERE path LIKE $1", + } + for name, query := range statements { + if err := m.prepare(name, query); err != nil { + return nil, err + } } - return m, nil } +// prepare is a helper to prepare a query for future execution +func (m *CockroachDBBackend) prepare(name, query string) error { + stmt, err := m.client.Prepare(query) + if err != nil { + return fmt.Errorf("failed to prepare '%s': %v", name, err) + } + m.statements[name] = stmt + return nil +} + // Put is used to insert or update an entry. func (m *CockroachDBBackend) Put(entry *Entry) error { defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) - _, err := m.client.Exec(m.put_query, entry.Key, entry.Value) + _, err := m.statements["put"].Exec(entry.Key, entry.Value) if err != nil { return err } @@ -81,11 +95,11 @@ func (m *CockroachDBBackend) Put(entry *Entry) error { } // Get is used to fetch and entry. -func (m *CockroachDBBackend) Get(fullPath string) (*Entry, error) { +func (m *CockroachDBBackend) Get(key string) (*Entry, error) { defer metrics.MeasureSince([]string{"cockroachdb", "get"}, time.Now()) var result []byte - err := m.client.QueryRow(m.get_query, fullPath).Scan(&result) + err := m.statements["get"].QueryRow(key).Scan(&result) if err == sql.ErrNoRows { return nil, nil } @@ -94,17 +108,17 @@ func (m *CockroachDBBackend) Get(fullPath string) (*Entry, error) { } ent := &Entry{ - Key: fullPath, + Key: key, Value: result, } return ent, nil } // Delete is used to permanently delete an entry -func (m *CockroachDBBackend) Delete(fullPath string) error { +func (m *CockroachDBBackend) Delete(key string) error { defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) - _, err := m.client.Exec(m.delete_query, fullPath) + _, err := m.statements["delete"].Exec(key) if err != nil { return err } @@ -116,7 +130,8 @@ func (m *CockroachDBBackend) Delete(fullPath string) error { func (m *CockroachDBBackend) List(prefix string) ([]string, error) { defer metrics.MeasureSince([]string{"cockroachdb", "list"}, time.Now()) - rows, err := m.client.Query(m.list_query, prefix) + likePrefix := prefix + "%" + rows, err := m.statements["list"].Query(likePrefix) if err != nil { return nil, err } diff --git a/physical/cockroachdb_test.go b/physical/cockroachdb_test.go index d98611168d2b..83e1ef237cb1 100644 --- a/physical/cockroachdb_test.go +++ b/physical/cockroachdb_test.go @@ -34,8 +34,8 @@ func TestCockroachDBBackend(t *testing.T) { } defer func() { - pg := b.(*CockroachDBBackend) - _, err := pg.client.Exec("TRUNCATE TABLE " + pg.table) + crdb := b.(*CockroachDBBackend) + _, err := crdb.client.Exec("TRUNCATE TABLE " + crdb.table) if err != nil { t.Fatalf("Failed to drop table: %v", err) } From 29fb9bec4d1d4991afc2508c5bb61576ee85e5aa Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Thu, 11 May 2017 23:20:32 -0400 Subject: [PATCH 03/10] Adding dockertest tests --- physical/cockroachdb_test.go | 64 +++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/physical/cockroachdb_test.go b/physical/cockroachdb_test.go index 83e1ef237cb1..756e27c4d61e 100644 --- a/physical/cockroachdb_test.go +++ b/physical/cockroachdb_test.go @@ -1,26 +1,75 @@ package physical import ( + "database/sql" + "fmt" "os" "testing" + dockertest "gopkg.in/ory-am/dockertest.v3" + "github.com/hashicorp/vault/helper/logformat" log "github.com/mgutz/logxi/v1" _ "github.com/lib/pq" ) -func TestCockroachDBBackend(t *testing.T) { - connURL := os.Getenv("CRURL") - if connURL == "" { - t.SkipNow() +func prepareCockroachDBTestContainer(t *testing.T) (cleanup func(), retURL, tableName string) { + tableName = os.Getenv("CR_TABLE") + if tableName == "" { + tableName = "vault_kv_store" + } + retURL = os.Getenv("CR_URL") + if retURL != "" { + return func() {}, retURL, tableName } - table := os.Getenv("CRTABLE") - if table == "" { - table = "vault_kv_store" + pool, err := dockertest.NewPool("") + if err != nil { + t.Fatalf("Failed to connect to docker: %s", err) } + dockerOptions := &dockertest.RunOptions{ + Repository: "cockroachdb/cockroach", + Tag: "latest", + Cmd: []string{"start", "--insecure"}, + } + resource, err := pool.RunWithOptions(dockerOptions) + if err != nil { + t.Fatalf("Could not start local CockroachDB docker container: %s", err) + } + + cleanup = func() { + err := pool.Purge(resource) + if err != nil { + t.Fatalf("Failed to cleanup local container: %s", err) + } + } + + retURL = fmt.Sprintf("postgresql://root@localhost:%s/?sslmode=disable", resource.GetPort("26257/tcp")) + database := "database" + tableName = database + ".vault_kv" + + // exponential backoff-retry + if err = pool.Retry(func() error { + var err error + db, err := sql.Open("postgres", retURL) + if err != nil { + return err + } + _, err = db.Exec("CREATE DATABASE database") + return err + }); err != nil { + cleanup() + t.Fatalf("Could not connect to docker: %s", err) + } + return cleanup, retURL, tableName +} + +func TestCockroachDBBackend(t *testing.T) { + cleanup, connURL, table := prepareCockroachDBTestContainer(t) + defer cleanup() + // Run vault tests logger := logformat.NewVaultLogger(log.LevelTrace) @@ -43,5 +92,4 @@ func TestCockroachDBBackend(t *testing.T) { testBackend(t, b) testBackend_ListPrefix(t, b) - } From c5c2f39563257599dde072f1acae9fe88c446538 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Thu, 11 May 2017 23:57:37 -0400 Subject: [PATCH 04/10] reorganizing --- physical/cockroachdb.go | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index aa6e097a6140..8a9ad5643e64 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -10,15 +10,19 @@ import ( log "github.com/mgutz/logxi/v1" "github.com/armon/go-metrics" + + // CockroachDB uses the Postgres SQL driver + _ "github.com/lib/pq" ) // CockroachDBBackend Backend is a physical backend that stores data // within a CockroachDB database. type CockroachDBBackend struct { - table string - client *sql.DB - statements map[string]*sql.Stmt - logger log.Logger + table string + client *sql.DB + rawStatements map[string]string + statements map[string]*sql.Stmt + logger log.Logger } // newCockroachDBBackend constructs a CockroachDB backend using the given @@ -50,22 +54,22 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, // Setup the backend m := &CockroachDBBackend{ - table: dbTable, - client: db, + table: dbTable, + client: db, + rawStatements: map[string]string{ + "put": "INSERT INTO " + dbTable + " VALUES($1, $2)" + + " ON CONFLICT (path) DO " + + " UPDATE SET (path, value) = ($1, $2)", + "get": "SELECT value FROM " + dbTable + " WHERE path = $1", + "delete": "DELETE FROM " + dbTable + " WHERE path = $1", + "list": "SELECT path FROM " + dbTable + " WHERE path LIKE $1", + }, statements: make(map[string]*sql.Stmt), logger: logger, } // Prepare all the statements required - statements := map[string]string{ - "put": "INSERT INTO " + dbTable + " VALUES($1, $2)" + - " ON CONFLICT (path) DO " + - " UPDATE SET (path, value) = ($1, $2)", - "get": "SELECT value FROM " + dbTable + " WHERE path = $1", - "delete": "DELETE FROM " + dbTable + " WHERE path = $1", - "list": "SELECT path FROM " + dbTable + " WHERE path LIKE $1", - } - for name, query := range statements { + for name, query := range m.rawStatements { if err := m.prepare(name, query); err != nil { return nil, err } From 42eca0a7edd3af49833d97b2073254c607a9c541 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Fri, 12 May 2017 09:06:02 -0400 Subject: [PATCH 05/10] Adding transaction support --- physical/cockroachdb.go | 82 ++++--- .../cockroachdb/cockroach-go/LICENSE | 202 ++++++++++++++++++ .../cockroachdb/cockroach-go/crdb/tx.go | 95 ++++++++ vendor/vendor.json | 10 + 4 files changed, 364 insertions(+), 25 deletions(-) create mode 100644 vendor/github.com/cockroachdb/cockroach-go/LICENSE create mode 100644 vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index 8a9ad5643e64..9548e253ff78 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -7,9 +7,9 @@ import ( "strings" "time" - log "github.com/mgutz/logxi/v1" - "github.com/armon/go-metrics" + "github.com/cockroachdb/cockroach-go/crdb" + log "github.com/mgutz/logxi/v1" // CockroachDB uses the Postgres SQL driver _ "github.com/lib/pq" @@ -53,7 +53,7 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, } // Setup the backend - m := &CockroachDBBackend{ + c := &CockroachDBBackend{ table: dbTable, client: db, rawStatements: map[string]string{ @@ -69,41 +69,38 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, } // Prepare all the statements required - for name, query := range m.rawStatements { - if err := m.prepare(name, query); err != nil { + for name, query := range c.rawStatements { + if err := c.prepare(name, query); err != nil { return nil, err } } - return m, nil + return c, nil } // prepare is a helper to prepare a query for future execution -func (m *CockroachDBBackend) prepare(name, query string) error { - stmt, err := m.client.Prepare(query) +func (c *CockroachDBBackend) prepare(name, query string) error { + stmt, err := c.client.Prepare(query) if err != nil { return fmt.Errorf("failed to prepare '%s': %v", name, err) } - m.statements[name] = stmt + c.statements[name] = stmt return nil } // Put is used to insert or update an entry. -func (m *CockroachDBBackend) Put(entry *Entry) error { +func (c *CockroachDBBackend) Put(entry *Entry) error { defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) - _, err := m.statements["put"].Exec(entry.Key, entry.Value) - if err != nil { - return err - } - return nil + _, err := c.statements["put"].Exec(entry.Key, entry.Value) + return err } // Get is used to fetch and entry. -func (m *CockroachDBBackend) Get(key string) (*Entry, error) { +func (c *CockroachDBBackend) Get(key string) (*Entry, error) { defer metrics.MeasureSince([]string{"cockroachdb", "get"}, time.Now()) var result []byte - err := m.statements["get"].QueryRow(key).Scan(&result) + err := c.statements["get"].QueryRow(key).Scan(&result) if err == sql.ErrNoRows { return nil, nil } @@ -119,23 +116,20 @@ func (m *CockroachDBBackend) Get(key string) (*Entry, error) { } // Delete is used to permanently delete an entry -func (m *CockroachDBBackend) Delete(key string) error { +func (c *CockroachDBBackend) Delete(key string) error { defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) - _, err := m.statements["delete"].Exec(key) - if err != nil { - return err - } - return nil + _, err := c.statements["delete"].Exec(key) + return err } // List is used to list all the keys under a given // prefix, up to the next prefix. -func (m *CockroachDBBackend) List(prefix string) ([]string, error) { +func (c *CockroachDBBackend) List(prefix string) ([]string, error) { defer metrics.MeasureSince([]string{"cockroachdb", "list"}, time.Now()) likePrefix := prefix + "%" - rows, err := m.statements["list"].Query(likePrefix) + rows, err := c.statements["list"].Query(likePrefix) if err != nil { return nil, err } @@ -162,3 +156,41 @@ func (m *CockroachDBBackend) List(prefix string) ([]string, error) { sort.Strings(keys) return keys, nil } + +// Transaction is used to run multiple entries via a transaction +func (c *CockroachDBBackend) Transaction(txns []TxnEntry) error { + if len(txns) == 0 { + return nil + } + + return crdb.ExecuteTx(c.client, func(tx *sql.Tx) error { + return c.transaction(tx, txns) + }) +} + +func (c *CockroachDBBackend) transaction(tx *sql.Tx, txns []TxnEntry) error { + deleteStmt, err := tx.Prepare(c.rawStatements["delete"]) + if err != nil { + return err + } + putStmt, err := tx.Prepare(c.rawStatements["put"]) + if err != nil { + return err + } + + for _, op := range txns { + switch op.Operation { + case DeleteOperation: + _, err := deleteStmt.Exec(op.Entry.Key) + if err != nil { + return err + } + case PutOperation: + _, err := putStmt.Exec(op.Entry.Key, op.Entry.Value) + if err != nil { + return err + } + } + } + return nil +} diff --git a/vendor/github.com/cockroachdb/cockroach-go/LICENSE b/vendor/github.com/cockroachdb/cockroach-go/LICENSE new file mode 100644 index 000000000000..829ea336da67 --- /dev/null +++ b/vendor/github.com/cockroachdb/cockroach-go/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {} + + 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. + diff --git a/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go b/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go new file mode 100644 index 000000000000..7c51b67f2beb --- /dev/null +++ b/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go @@ -0,0 +1,95 @@ +// Copyright 2016 The Cockroach 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. +// +// Author: Andrei Matei (andrei@cockroachlabs.com) + +// Package crdb provides helpers for using CockroachDB in client +// applications. +package crdb + +import ( + "database/sql" + + "github.com/lib/pq" +) + +// AmbiguousCommitError represents an error that left a transaction in an +// ambiguous state: unclear if it committed or not. +type AmbiguousCommitError struct { + error +} + +// ExecuteTx runs fn inside a transaction and retries it as needed. +// On non-retryable failures, the transaction is aborted and rolled +// back; on success, the transaction is committed. +// There are cases where the state of a transaction is inherently ambiguous: if +// we err on RELEASE with a communication error it's unclear if the transaction +// has been committed or not (similar to erroring on COMMIT in other databases). +// In that case, we return AmbiguousCommitError. +// +// For more information about CockroachDB's transaction model see +// https://cockroachlabs.com/docs/transactions.html. +// +// NOTE: the supplied exec closure should not have external side +// effects beyond changes to the database. +func ExecuteTx(db *sql.DB, fn func(*sql.Tx) error) (err error) { + // Start a transaction. + var tx *sql.Tx + tx, err = db.Begin() + if err != nil { + return err + } + defer func() { + if err == nil { + // Ignore commit errors. The tx has already been committed by RELEASE. + _ = tx.Commit() + } else { + // We always need to execute a Rollback() so sql.DB releases the + // connection. + _ = tx.Rollback() + } + }() + // Specify that we intend to retry this txn in case of CockroachDB retryable + // errors. + if _, err = tx.Exec("SAVEPOINT cockroach_restart"); err != nil { + return err + } + + for { + released := false + err = fn(tx) + if err == nil { + // RELEASE acts like COMMIT in CockroachDB. We use it since it gives us an + // opportunity to react to retryable errors, whereas tx.Commit() doesn't. + released = true + if _, err = tx.Exec("RELEASE SAVEPOINT cockroach_restart"); err == nil { + return nil + } + } + // We got an error; let's see if it's a retryable one and, if so, restart. We look + // for either the standard PG errcode SerializationFailureError:40001 or the Cockroach extension + // errcode RetriableError:CR000. The Cockroach extension has been removed server-side, but support + // for it has been left here for now to maintain backwards compatibility. + pqErr, ok := err.(*pq.Error) + if retryable := ok && (pqErr.Code == "CR000" || pqErr.Code == "40001"); !retryable { + if released { + err = &AmbiguousCommitError{err} + } + return err + } + if _, err = tx.Exec("ROLLBACK TO SAVEPOINT cockroach_restart"); err != nil { + return err + } + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 7d340003edde..f30c3965a5ad 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -402,6 +402,12 @@ "revision": "7d649b46cdc2cd2ed102d350688a75a4fd7778c6", "revisionTime": "2016-11-21T13:51:53Z" }, + { + "checksumSHA1": "yQv3AchZYDl2gdQpHPT+NWc1kh0=", + "path": "github.com/cockroachdb/cockroach-go/crdb", + "revision": "c8f63e30437f4b80673b631763130d0fef87dd76", + "revisionTime": "2017-05-08T22:31:14Z" + }, { "checksumSHA1": "7BC2/27NId9xaPDB5w3nWN2mn9A=", "path": "github.com/coreos/etcd/auth/authpb", @@ -1722,6 +1728,10 @@ "revision": "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b", "revisionTime": "2017-04-07T17:21:22Z" }, + { + "path": "https://github.com/cockroachdb/cockroach-go/crdb", + "revision": "" + }, { "checksumSHA1": "s7O2cNvVNkPwgHs4+rfyDscl9Xw=", "path": "layeh.com/radius", From 3873ab4ca251da730a459b1e7361809a8441325a Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Fri, 12 May 2017 09:14:53 -0400 Subject: [PATCH 06/10] Minor edits to transaction handling --- physical/cockroachdb.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index 9548e253ff78..19f7823ebc86 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -181,15 +181,14 @@ func (c *CockroachDBBackend) transaction(tx *sql.Tx, txns []TxnEntry) error { for _, op := range txns { switch op.Operation { case DeleteOperation: - _, err := deleteStmt.Exec(op.Entry.Key) - if err != nil { - return err - } + _, err = deleteStmt.Exec(op.Entry.Key) case PutOperation: - _, err := putStmt.Exec(op.Entry.Key, op.Entry.Value) - if err != nil { - return err - } + _, err = putStmt.Exec(op.Entry.Key, op.Entry.Value) + default: + return fmt.Errorf("%q is not a supported transaction operation", op.Operation) + } + if err != nil { + return err } } return nil From 3978073cfcf81023986d50fd8414004f185cdd43 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Fri, 12 May 2017 10:29:29 -0400 Subject: [PATCH 07/10] Splitting out transactional tests --- physical/cockroachdb.go | 11 +++++-- physical/cockroachdb_test.go | 17 +++++++--- physical/physical_test.go | 54 +++++++++++++++++++++++++++++++ physical/transactions_test.go | 60 ++++------------------------------- 4 files changed, 82 insertions(+), 60 deletions(-) diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index 19f7823ebc86..c5aadc66e695 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -92,7 +92,10 @@ func (c *CockroachDBBackend) Put(entry *Entry) error { defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) _, err := c.statements["put"].Exec(entry.Key, entry.Value) - return err + if err != nil { + return err + } + return nil } // Get is used to fetch and entry. @@ -120,7 +123,10 @@ func (c *CockroachDBBackend) Delete(key string) error { defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) _, err := c.statements["delete"].Exec(key) - return err + if err != nil { + return err + } + return nil } // List is used to list all the keys under a given @@ -159,6 +165,7 @@ func (c *CockroachDBBackend) List(prefix string) ([]string, error) { // Transaction is used to run multiple entries via a transaction func (c *CockroachDBBackend) Transaction(txns []TxnEntry) error { + defer metrics.MeasureSince([]string{"cockroachdb", "transaction"}, time.Now()) if len(txns) == 0 { return nil } diff --git a/physical/cockroachdb_test.go b/physical/cockroachdb_test.go index 756e27c4d61e..6d92c7cd4951 100644 --- a/physical/cockroachdb_test.go +++ b/physical/cockroachdb_test.go @@ -83,13 +83,20 @@ func TestCockroachDBBackend(t *testing.T) { } defer func() { - crdb := b.(*CockroachDBBackend) - _, err := crdb.client.Exec("TRUNCATE TABLE " + crdb.table) - if err != nil { - t.Fatalf("Failed to drop table: %v", err) - } + truncate(t, b) }() testBackend(t, b) + truncate(t, b) testBackend_ListPrefix(t, b) + truncate(t, b) + testTransactionalBackend(t, b) +} + +func truncate(t *testing.T, b Backend) { + crdb := b.(*CockroachDBBackend) + _, err := crdb.client.Exec("TRUNCATE TABLE " + crdb.table) + if err != nil { + t.Fatalf("Failed to drop table: %v", err) + } } diff --git a/physical/physical_test.go b/physical/physical_test.go index de1b9cbfa4cb..6921a89d49ff 100644 --- a/physical/physical_test.go +++ b/physical/physical_test.go @@ -634,3 +634,57 @@ func testEventuallyConsistentBackend_ListPrefix(t *testing.T, b Backend, d delay } } + +func testTransactionalBackend(t *testing.T, b Backend) { + tb, ok := b.(Transactional) + if !ok { + t.Fatal("Not a transactional backend") + } + + txns := setupTransactions(t, b) + + if err := tb.Transaction(txns); err != nil { + t.Fatal(err) + } + + keys, err := b.List("") + if err != nil { + t.Fatal(err) + } + + expected := []string{"foo", "zip"} + + sort.Strings(keys) + sort.Strings(expected) + if !reflect.DeepEqual(keys, expected) { + t.Fatalf("mismatch: expected\n%#v\ngot\n%#v\n", expected, keys) + } + + entry, err := b.Get("foo") + if err != nil { + t.Fatal(err) + } + if entry == nil { + t.Fatal("got nil entry") + } + if entry.Value == nil { + t.Fatal("got nil value") + } + if string(entry.Value) != "bar3" { + t.Fatal("updates did not apply correctly") + } + + entry, err = b.Get("zip") + if err != nil { + t.Fatal(err) + } + if entry == nil { + t.Fatal("got nil entry") + } + if entry.Value == nil { + t.Fatal("got nil value") + } + if string(entry.Value) != "zap3" { + t.Fatal("updates did not apply correctly") + } +} diff --git a/physical/transactions_test.go b/physical/transactions_test.go index e365a95974e9..ab5d02bb7dd2 100644 --- a/physical/transactions_test.go +++ b/physical/transactions_test.go @@ -89,60 +89,14 @@ func TestPseudo_SuccessfulTransaction(t *testing.T) { logger := logformat.NewVaultLogger(log.LevelTrace) p := newFaultyPseudo(logger, nil) - txns := setupPseudo(p, t) - - if err := p.Transaction(txns); err != nil { - t.Fatal(err) - } - - keys, err := p.List("") - if err != nil { - t.Fatal(err) - } - - expected := []string{"foo", "zip"} - - sort.Strings(keys) - sort.Strings(expected) - if !reflect.DeepEqual(keys, expected) { - t.Fatalf("mismatch: expected\n%#v\ngot\n%#v\n", expected, keys) - } - - entry, err := p.Get("foo") - if err != nil { - t.Fatal(err) - } - if entry == nil { - t.Fatal("got nil entry") - } - if entry.Value == nil { - t.Fatal("got nil value") - } - if string(entry.Value) != "bar3" { - t.Fatal("updates did not apply correctly") - } - - entry, err = p.Get("zip") - if err != nil { - t.Fatal(err) - } - if entry == nil { - t.Fatal("got nil entry") - } - if entry.Value == nil { - t.Fatal("got nil value") - } - if string(entry.Value) != "zap3" { - t.Fatal("updates did not apply correctly") - } + testTransactionalBackend(t, p) } func TestPseudo_FailedTransaction(t *testing.T) { logger := logformat.NewVaultLogger(log.LevelTrace) p := newFaultyPseudo(logger, []string{"zip"}) - txns := setupPseudo(p, t) - + txns := setupTransactions(t, p) if err := p.Transaction(txns); err == nil { t.Fatal("expected error during transaction") } @@ -189,26 +143,26 @@ func TestPseudo_FailedTransaction(t *testing.T) { } } -func setupPseudo(p *faultyPseudo, t *testing.T) []TxnEntry { +func setupTransactions(t *testing.T, b Backend) []TxnEntry { // Add a few keys so that we test rollback with deletion - if err := p.Put(&Entry{ + if err := b.Put(&Entry{ Key: "foo", Value: []byte("bar"), }); err != nil { t.Fatal(err) } - if err := p.Put(&Entry{ + if err := b.Put(&Entry{ Key: "zip", Value: []byte("zap"), }); err != nil { t.Fatal(err) } - if err := p.Put(&Entry{ + if err := b.Put(&Entry{ Key: "deleteme", }); err != nil { t.Fatal(err) } - if err := p.Put(&Entry{ + if err := b.Put(&Entry{ Key: "deleteme2", }); err != nil { t.Fatal(err) From 16f455c8d659bcc198eac7b79d85e7e624650381 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Tue, 20 Jun 2017 21:17:17 -0400 Subject: [PATCH 08/10] updating deps --- .../cockroachdb/cockroach-go/crdb/tx.go | 17 ++++++++++++++++- vendor/vendor.json | 6 +++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go b/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go index 7c51b67f2beb..e6c82a7c044b 100644 --- a/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go +++ b/vendor/github.com/cockroachdb/cockroach-go/crdb/tx.go @@ -50,6 +50,21 @@ func ExecuteTx(db *sql.DB, fn func(*sql.Tx) error) (err error) { if err != nil { return err } + return ExecuteInTx(tx, func() error { return fn(tx) }) +} + +type Tx interface { + Exec(query string, args ...interface{}) (sql.Result, error) + Commit() error + Rollback() error +} + +// ExecuteInTx runs fn inside tx which should already have begun. +// *WARNING*: Do not execute any statements on the supplied tx before calling this function. +// ExecuteInTx will only retry statements that are performed within the supplied +// closure (fn). Any statements performed on the tx before ExecuteInTx is invoked will *not* +// be re-run if the transaction needs to be retried. +func ExecuteInTx(tx Tx, fn func() error) (err error) { defer func() { if err == nil { // Ignore commit errors. The tx has already been committed by RELEASE. @@ -68,7 +83,7 @@ func ExecuteTx(db *sql.DB, fn func(*sql.Tx) error) (err error) { for { released := false - err = fn(tx) + err = fn() if err == nil { // RELEASE acts like COMMIT in CockroachDB. We use it since it gives us an // opportunity to react to retryable errors, whereas tx.Commit() doesn't. diff --git a/vendor/vendor.json b/vendor/vendor.json index 8fe2bd1107ce..414d7502ae61 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -433,10 +433,10 @@ "revisionTime": "2017-05-25T20:16:49Z" }, { - "checksumSHA1": "yQv3AchZYDl2gdQpHPT+NWc1kh0=", + "checksumSHA1": "3sQ0WnR+0+5mmFv1J8y84ONOX8M=", "path": "github.com/cockroachdb/cockroach-go/crdb", - "revision": "c8f63e30437f4b80673b631763130d0fef87dd76", - "revisionTime": "2017-05-08T22:31:14Z" + "revision": "41e9ceadabbc9fdff3017bc6b3d320ae74bd0129", + "revisionTime": "2017-06-14T20:07:24Z" }, { "checksumSHA1": "7BC2/27NId9xaPDB5w3nWN2mn9A=", From a078c6eb7bd2476c9544b867f42b0516f29e7791 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Tue, 20 Jun 2017 21:17:29 -0400 Subject: [PATCH 09/10] adding permit pool --- physical/cockroachdb.go | 35 ++++++++++++++++++++++++++++++++++- physical/cockroachdb_test.go | 2 +- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index c5aadc66e695..011126cbfaf8 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -4,11 +4,14 @@ import ( "database/sql" "fmt" "sort" + "strconv" "strings" "time" "github.com/armon/go-metrics" "github.com/cockroachdb/cockroach-go/crdb" + "github.com/hashicorp/errwrap" + "github.com/hashicorp/vault/helper/strutil" log "github.com/mgutz/logxi/v1" // CockroachDB uses the Postgres SQL driver @@ -23,6 +26,7 @@ type CockroachDBBackend struct { rawStatements map[string]string statements map[string]*sql.Stmt logger log.Logger + permitPool *PermitPool } // newCockroachDBBackend constructs a CockroachDB backend using the given @@ -39,6 +43,19 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, dbTable = "vault_kv_store" } + maxParStr, ok := conf["max_parallel"] + var maxParInt int + var err error + if ok { + maxParInt, err = strconv.Atoi(maxParStr) + if err != nil { + return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err) + } + if logger.IsDebug() { + logger.Debug("mysql: max_parallel set", "max_parallel", maxParInt) + } + } + // Create CockroachDB handle for the database. db, err := sql.Open("postgres", connURL) if err != nil { @@ -66,6 +83,7 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, }, statements: make(map[string]*sql.Stmt), logger: logger, + permitPool: NewPermitPool(maxParInt), } // Prepare all the statements required @@ -91,6 +109,9 @@ func (c *CockroachDBBackend) prepare(name, query string) error { func (c *CockroachDBBackend) Put(entry *Entry) error { defer metrics.MeasureSince([]string{"cockroachdb", "put"}, time.Now()) + c.permitPool.Acquire() + defer c.permitPool.Release() + _, err := c.statements["put"].Exec(entry.Key, entry.Value) if err != nil { return err @@ -102,6 +123,9 @@ func (c *CockroachDBBackend) Put(entry *Entry) error { func (c *CockroachDBBackend) Get(key string) (*Entry, error) { defer metrics.MeasureSince([]string{"cockroachdb", "get"}, time.Now()) + c.permitPool.Acquire() + defer c.permitPool.Release() + var result []byte err := c.statements["get"].QueryRow(key).Scan(&result) if err == sql.ErrNoRows { @@ -122,6 +146,9 @@ func (c *CockroachDBBackend) Get(key string) (*Entry, error) { func (c *CockroachDBBackend) Delete(key string) error { defer metrics.MeasureSince([]string{"cockroachdb", "delete"}, time.Now()) + c.permitPool.Acquire() + defer c.permitPool.Release() + _, err := c.statements["delete"].Exec(key) if err != nil { return err @@ -134,6 +161,9 @@ func (c *CockroachDBBackend) Delete(key string) error { func (c *CockroachDBBackend) List(prefix string) ([]string, error) { defer metrics.MeasureSince([]string{"cockroachdb", "list"}, time.Now()) + c.permitPool.Acquire() + defer c.permitPool.Release() + likePrefix := prefix + "%" rows, err := c.statements["list"].Query(likePrefix) if err != nil { @@ -155,7 +185,7 @@ func (c *CockroachDBBackend) List(prefix string) ([]string, error) { keys = append(keys, key) } else if i != -1 { // Add truncated 'folder' paths - keys = appendIfMissing(keys, string(key[:i+1])) + keys = strutil.AppendIfMissing(keys, string(key[:i+1])) } } @@ -170,6 +200,9 @@ func (c *CockroachDBBackend) Transaction(txns []TxnEntry) error { return nil } + c.permitPool.Acquire() + defer c.permitPool.Release() + return crdb.ExecuteTx(c.client, func(tx *sql.Tx) error { return c.transaction(tx, txns) }) diff --git a/physical/cockroachdb_test.go b/physical/cockroachdb_test.go index 6d92c7cd4951..35e186f5b036 100644 --- a/physical/cockroachdb_test.go +++ b/physical/cockroachdb_test.go @@ -31,7 +31,7 @@ func prepareCockroachDBTestContainer(t *testing.T) (cleanup func(), retURL, tabl dockerOptions := &dockertest.RunOptions{ Repository: "cockroachdb/cockroach", - Tag: "latest", + Tag: "release-1.0", Cmd: []string{"start", "--insecure"}, } resource, err := pool.RunWithOptions(dockerOptions) From eb9c48627ed8db004715d27f1a186d55991a2050 Mon Sep 17 00:00:00 2001 From: Chris Hoffman Date: Sun, 23 Jul 2017 08:42:37 -0400 Subject: [PATCH 10/10] adding cockroachdb docs --- physical/cockroachdb.go | 2 +- .../configuration/storage/cockroachdb.html.md | 66 +++++++++++++++++++ .../configuration/storage/postgresql.html.md | 4 +- website/source/layouts/docs.erb | 6 ++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 website/source/docs/configuration/storage/cockroachdb.html.md diff --git a/physical/cockroachdb.go b/physical/cockroachdb.go index 011126cbfaf8..b904858cd1bc 100644 --- a/physical/cockroachdb.go +++ b/physical/cockroachdb.go @@ -52,7 +52,7 @@ func newCockroachDBBackend(conf map[string]string, logger log.Logger) (Backend, return nil, errwrap.Wrapf("failed parsing max_parallel parameter: {{err}}", err) } if logger.IsDebug() { - logger.Debug("mysql: max_parallel set", "max_parallel", maxParInt) + logger.Debug("cockroachdb: max_parallel set", "max_parallel", maxParInt) } } diff --git a/website/source/docs/configuration/storage/cockroachdb.html.md b/website/source/docs/configuration/storage/cockroachdb.html.md new file mode 100644 index 000000000000..28f569996c17 --- /dev/null +++ b/website/source/docs/configuration/storage/cockroachdb.html.md @@ -0,0 +1,66 @@ +--- +layout: "docs" +page_title: "CockroachDB - Storage Backends - Configuration" +sidebar_current: "docs-configuration-storage-cockroachdb" +description: |- + The CockroachDB storage backend is used to persist Vault's data in a CockroachDB + server or cluster. +--- + +# CockroachDB Storage Backend + +The CockroachDB storage backend is used to persist Vault's data in a +[CockroachDB][cockroachdb] server or cluster. + +- **No High Availability** – the CockroachDB storage backend does not support + high availability. + +- **Community Supported** – the CockroachDB storage backend is supported by the + community. While it has undergone development and review by HashiCorp + employees, they may not be as knowledgeable about the technology. + +```hcl +storage "cockroachdb" { + connection_url = "postgres://user123:secret123!@localhost:5432/vault" +} +``` + +**Note** - CockroachDB is compatible with the PostgreSQL database driver and +uses that driver to interact with the database. + +## `cockroachdb` Parameters + +- `connection_url` `(string: )` – Specifies the connection string to + use to authenticate and connect to CockroachDB. A full list of supported + parameters can be found in [the pq library documentation][pglib]. For example + connection string URLs, see the examples section below. + +- `table` `(string: "vault_kv_store")` – Specifies the name of the table in + which to write Vault data. This table must already exist (Vault will not + attempt to create it). + +- `max_parallel` `(string: "128")` – Specifies the maximum number of concurrent + requests to CockroachDB. + +## `cockroachdb` Examples + +This example shows connecting to a PostgresSQL cluster using full SSL +verification (recommended). + +```hcl +storage "cockroachdb" { + connection_url = "postgres://user:pass@localhost:5432/database?sslmode=verify-full" +} +``` + +To disable SSL verification (not recommended), replace `verify-full` with +`disable`: + +```hcl +storage "cockroachdb" { + connection_url = "postgres://user:pass@localhost:5432/database?sslmode=disable" +} +``` + +[cockroachdb]: https://www.cockroachlabs.com/ +[pglib]: https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters diff --git a/website/source/docs/configuration/storage/postgresql.html.md b/website/source/docs/configuration/storage/postgresql.html.md index 238af3e90b98..bb7a3f3ae5fa 100644 --- a/website/source/docs/configuration/storage/postgresql.html.md +++ b/website/source/docs/configuration/storage/postgresql.html.md @@ -26,7 +26,7 @@ storage "postgresql" { } ``` -The PostgresSQL storage backend does not automatically create the table. Here is +The PostgreSQL storage backend does not automatically create the table. Here is some sample SQL to create the schema and indexes. ```sql @@ -86,7 +86,7 @@ LANGUAGE plpgsql; ### Custom SSL Verification -This example shows connecting to a PostgresSQL cluster using full SSL +This example shows connecting to a PostgreSQL cluster using full SSL verification (recommended). ```hcl diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index 1cf1c6464df9..e51ec4cf961d 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -100,9 +100,15 @@ > Azure + > + CockroachDB + > Consul + > + CouchDB + > DynamoDB