Add ha_enabled for mysql backend (#5122)

* Slight cleanup around mysql ha lock implementation

* Removes some duplication around lock table naming
* Escapes lock table name with backticks to handle weird characters
* Lock table defaults to regular table name + "_lock"
* Drop lock table after tests run

* Add `ha_enabled` option for mysql storage

It defaults to false, and we gate a few things like creating the lock
table and preparing lock related statements on it
This commit is contained in:
brianvans
2018-08-16 11:03:16 -07:00
committed by Brian Kassouf
parent c3e733623e
commit 801eddf5f8
3 changed files with 71 additions and 49 deletions

View File

@@ -37,6 +37,7 @@ const mysqlTLSKey = "default"
// within MySQL database. // within MySQL database.
type MySQLBackend struct { type MySQLBackend struct {
dbTable string dbTable string
dbLockTable string
client *sql.DB client *sql.DB
statements map[string]*sql.Stmt statements map[string]*sql.Stmt
logger log.Logger logger log.Logger
@@ -44,6 +45,7 @@ type MySQLBackend struct {
conf map[string]string conf map[string]string
redirectHost string redirectHost string
redirectPort int64 redirectPort int64
haEnabled bool
} }
// NewMySQLBackend constructs a MySQL backend using the given API client and // NewMySQLBackend constructs a MySQL backend using the given API client and
@@ -64,7 +66,7 @@ func NewMySQLBackend(conf map[string]string, logger log.Logger) (physical.Backen
if !ok { if !ok {
table = "vault" table = "vault"
} }
dbTable := "`" + database + "`" + "." + "`" + table + "`" dbTable := "`" + database + "`.`" + table + "`"
maxParStr, ok := conf["max_parallel"] maxParStr, ok := conf["max_parallel"]
var maxParInt int var maxParInt int
@@ -115,52 +117,72 @@ func NewMySQLBackend(conf map[string]string, logger log.Logger) (physical.Backen
} }
} }
// Default value for ha_enabled
haEnabledStr, ok := conf["ha_enabled"]
if !ok {
haEnabledStr = "false"
}
haEnabled, err := strconv.ParseBool(haEnabledStr)
if err != nil {
return nil, fmt.Errorf("value [%v] of 'ha_enabled' could not be understood", haEnabledStr)
}
locktable, ok := conf["lock_table"] locktable, ok := conf["lock_table"]
if !ok { if !ok {
locktable = "vault_lock" locktable = table + "_lock"
} }
dbLockTable := database + "." + locktable dbLockTable := "`" + database + "`.`" + locktable + "`"
// Check table exists // Only create lock table if ha_enabled is true
var lockTableExist bool if haEnabled {
lockTableRows, err := db.Query("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?", locktable, database) // Check table exists
var lockTableExist bool
lockTableRows, err := db.Query("SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_NAME = ? AND TABLE_SCHEMA = ?", locktable, database)
if err != nil { if err != nil {
return nil, errwrap.Wrapf("failed to check mysql table exist: {{err}}", err) return nil, errwrap.Wrapf("failed to check mysql table exist: {{err}}", err)
} }
defer lockTableRows.Close() defer lockTableRows.Close()
lockTableExist = lockTableRows.Next() lockTableExist = lockTableRows.Next()
// Create the required table if it doesn't exists. // Create the required table if it doesn't exists.
if !lockTableExist { if !lockTableExist {
create_query := "CREATE TABLE IF NOT EXISTS " + dbLockTable + create_query := "CREATE TABLE IF NOT EXISTS " + dbLockTable +
" (node_job varchar(512), current_leader varchar(512), PRIMARY KEY (node_job))" " (node_job varchar(512), current_leader varchar(512), PRIMARY KEY (node_job))"
if _, err := db.Exec(create_query); err != nil { if _, err := db.Exec(create_query); err != nil {
return nil, errwrap.Wrapf("failed to create mysql table: {{err}}", err) return nil, errwrap.Wrapf("failed to create mysql table: {{err}}", err)
}
} }
} }
// Setup the backend. // Setup the backend.
m := &MySQLBackend{ m := &MySQLBackend{
dbTable: dbTable, dbTable: dbTable,
client: db, dbLockTable: dbLockTable,
statements: make(map[string]*sql.Stmt), client: db,
logger: logger, statements: make(map[string]*sql.Stmt),
permitPool: physical.NewPermitPool(maxParInt), logger: logger,
conf: conf, permitPool: physical.NewPermitPool(maxParInt),
conf: conf,
haEnabled: haEnabled,
} }
// Prepare all the statements required // Prepare all the statements required
statements := map[string]string{ statements := map[string]string{
"put": "INSERT INTO " + dbTable + "put": "INSERT INTO " + dbTable +
" VALUES( ?, ? ) ON DUPLICATE KEY UPDATE vault_value=VALUES(vault_value)", " VALUES( ?, ? ) ON DUPLICATE KEY UPDATE vault_value=VALUES(vault_value)",
"get": "SELECT vault_value FROM " + dbTable + " WHERE vault_key = ?", "get": "SELECT vault_value FROM " + dbTable + " WHERE vault_key = ?",
"delete": "DELETE FROM " + dbTable + " WHERE vault_key = ?", "delete": "DELETE FROM " + dbTable + " WHERE vault_key = ?",
"list": "SELECT vault_key FROM " + dbTable + " WHERE vault_key LIKE ?", "list": "SELECT vault_key FROM " + dbTable + " WHERE vault_key LIKE ?",
"get_lock": "SELECT current_leader FROM " + dbLockTable + " WHERE node_job = ?",
"used_lock": "SELECT IS_USED_LOCK(?)",
} }
// Only prepare ha-related statements if we need them
if haEnabled {
statements["get_lock"] = "SELECT current_leader FROM " + dbLockTable + " WHERE node_job = ?"
statements["used_lock"] = "SELECT IS_USED_LOCK(?)"
}
for name, query := range statements { for name, query := range statements {
if err := m.prepare(name, query); err != nil { if err := m.prepare(name, query); err != nil {
return nil, err return nil, err
@@ -364,7 +386,7 @@ func (m *MySQLBackend) LockWith(key, value string) (physical.Lock, error) {
} }
func (m *MySQLBackend) HAEnabled() bool { func (m *MySQLBackend) HAEnabled() bool {
return true return m.haEnabled
} }
// MySQLHALock is a MySQL Lock implementation for the HABackend // MySQLHALock is a MySQL Lock implementation for the HABackend
@@ -549,18 +571,6 @@ func NewMySQLLock(in *MySQLBackend, l log.Logger, key, value string) (*MySQLLock
// the rest of the MySQL backend and any cleanup that might need to be done. // the rest of the MySQL backend and any cleanup that might need to be done.
conn, _ := NewMySQLClient(in.conf, in.logger) conn, _ := NewMySQLClient(in.conf, in.logger)
table, ok := in.conf["lock_table"]
if !ok {
table = "vault_lock"
}
database, ok := in.conf["database"]
if !ok {
database = "vault"
}
dbTable := database + "." + table
m := &MySQLLock{ m := &MySQLLock{
parentConn: in, parentConn: in,
in: conn, in: conn,
@@ -571,7 +581,7 @@ func NewMySQLLock(in *MySQLBackend, l log.Logger, key, value string) (*MySQLLock
} }
statements := map[string]string{ statements := map[string]string{
"put": "INSERT INTO " + dbTable + "put": "INSERT INTO " + in.dbLockTable +
" VALUES( ?, ? ) ON DUPLICATE KEY UPDATE current_leader=VALUES(current_leader)", " VALUES( ?, ? ) ON DUPLICATE KEY UPDATE current_leader=VALUES(current_leader)",
} }

View File

@@ -47,7 +47,7 @@ func TestMySQLBackend(t *testing.T) {
defer func() { defer func() {
mysql := b.(*MySQLBackend) mysql := b.(*MySQLBackend)
_, err := mysql.client.Exec("DROP TABLE " + mysql.dbTable) _, err := mysql.client.Exec("DROP TABLE IF EXISTS " + mysql.dbTable + " ," + mysql.dbLockTable)
if err != nil { if err != nil {
t.Fatalf("Failed to drop table: %v", err) t.Fatalf("Failed to drop table: %v", err)
} }
@@ -80,11 +80,12 @@ func TestMySQLHABackend(t *testing.T) {
logger := logging.NewVaultLogger(log.Debug) logger := logging.NewVaultLogger(log.Debug)
b, err := NewMySQLBackend(map[string]string{ b, err := NewMySQLBackend(map[string]string{
"address": address, "address": address,
"database": database, "database": database,
"table": table, "table": table,
"username": username, "username": username,
"password": password, "password": password,
"ha_enabled": "true",
}, logger) }, logger)
if err != nil { if err != nil {
@@ -93,7 +94,7 @@ func TestMySQLHABackend(t *testing.T) {
defer func() { defer func() {
mysql := b.(*MySQLBackend) mysql := b.(*MySQLBackend)
_, err := mysql.client.Exec("DROP TABLE " + mysql.dbTable) _, err := mysql.client.Exec("DROP TABLE IF EXISTS " + mysql.dbTable + " ," + mysql.dbLockTable)
if err != nil { if err != nil {
t.Fatalf("Failed to drop table: %v", err) t.Fatalf("Failed to drop table: %v", err)
} }

View File

@@ -56,6 +56,17 @@ Additionally, Vault requires the following authentication information.
- `password` `(string: <required)` Specifies the MySQL password to connect to - `password` `(string: <required)` Specifies the MySQL password to connect to
the database. the database.
### High Availability Parameters
- `ha_enabled` `(string: "true")` - Specifies if high availability mode is
enabled. This is a boolean value, but it is specified as a string like "true"
or "false".
- `lock_table` `(string: "vault_lock")` Specifies the name of the table to
use for storing high availability information. By default, this is the name
of the `table` suffixed with `_lock`. If the table does not exist, Vault will
attempt to create it.
## `mysql` Examples ## `mysql` Examples
### Custom Database and Table ### Custom Database and Table