Skip to content

Commit

Permalink
Merge pull request #151 from iegomez/fix/add-db-ping-retries
Browse files Browse the repository at this point in the history
Add connect_tries option for DB backends.
  • Loading branch information
iegomez committed Feb 11, 2021
2 parents 3acfc21 + 1efdaab commit f0eeb85
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 42 deletions.
91 changes: 55 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -499,20 +499,21 @@ The `postgres` backend allows to specify queries for user, superuser and acl ch

The following `auth_opt_` options are supported:

| Option | default | Mandatory | Meaning |
| -------------- | ----------------- | :---------: | ------------------------ |
| pg_host | localhost | | hostname/address
| pg_port | 5432 | | TCP port
| pg_user | | Y | username
| pg_password | | Y | password
| pg_dbname | | Y | database name
| pg_userquery | | Y | SQL for users
| pg_superquery | | N | SQL for superusers
| pg_aclquery | | N | SQL for ACLs
| pg_sslmode | disable | N | SSL/TLS mode.
| pg_sslcert | | N | SSL/TLS Client Cert.
| pg_sslkey | | N | SSL/TLS Client Cert. Key
| pg_sslrootcert | | N | SSL/TLS Root Cert
| Option | default | Mandatory | Meaning |
| --------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
| pg_host | localhost | | hostname/address |
| pg_port | 5432 | | TCP port |
| pg_user | | Y | username |
| pg_password | | Y | password |
| pg_dbname | | Y | database name |
| pg_userquery | | Y | SQL for users |
| pg_superquery | | N | SQL for superusers |
| pg_aclquery | | N | SQL for ACLs |
| pg_sslmode | disable | N | SSL/TLS mode. |
| pg_sslcert | | N | SSL/TLS Client Cert. |
| pg_sslkey | | N | SSL/TLS Client Cert. Key |
| pg_sslrootcert | | N | SSL/TLS Root Cert |
| pg_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |

Depending on the sslmode given, sslcert, sslkey and sslrootcert will be used. Options for sslmode are:

Expand Down Expand Up @@ -569,12 +570,18 @@ auth_opt_pg_port 5432
auth_opt_pg_dbname appserver
auth_opt_pg_user appserver
auth_opt_pg_password appserver
auth_opt_pg_connect_tries 5
auth_opt_pg_userquery select password_hash from "user" where username = $1 and is_active = true limit 1
auth_opt_pg_superquery select count(*) from "user" where username = $1 and is_admin = true
auth_opt_pg_aclquery select distinct 'application/' || a.id || '/#' from "user" u inner join organization_user ou on ou.user_id = u.id inner join organization o on o.id = ou.organization_id inner join application a on a.organization_id = o.id where u.username = $1 and $2 = $2
```

**DB connect tries**: on startup, depending on `pg_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
but you may choose to ping a max amount of times by setting any positive number.
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
Expand Down Expand Up @@ -635,22 +642,23 @@ auth_opt_mysql_allow_native_passwords true

Supported options for `mysql` are:

| Option | default | Mandatory | Meaning |
| -------------- | ----------------- | :---------: | ------------------------ |
| mysql_host | localhost | N | hostname/address
| mysql_port | 3306 | N | TCP port
| mysql_user | | Y | username
| mysql_password | | Y | password
| mysql_dbname | | Y | database name
| mysql_userquery | | Y | SQL for users
| mysql_superquery | | N | SQL for superusers
| mysql_aclquery | | N | SQL for ACLs
| mysql_sslmode | disable | N | SSL/TLS mode.
| mysql_sslcert | | N | SSL/TLS Client Cert.
| mysql_sslkey | | N | SSL/TLS Client Cert. Key
| mysql_sslrootcert | | N | SSL/TLS Root Cert
| mysql_protocol | tcp | N | Connection protocol
| mysql_socket | | N | Unix socket path
| Option | default | Mandatory | Meaning |
| ------------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
| mysql_host | localhost | N | hostname/address |
| mysql_port | 3306 | N | TCP port |
| mysql_user | | Y | username |
| mysql_password | | Y | password |
| mysql_dbname | | Y | database name |
| mysql_userquery | | Y | SQL for users |
| mysql_superquery | | N | SQL for superusers |
| mysql_aclquery | | N | SQL for ACLs |
| mysql_sslmode | disable | N | SSL/TLS mode. |
| mysql_sslcert | | N | SSL/TLS Client Cert. |
| mysql_sslkey | | N | SSL/TLS Client Cert. Key |
| mysql_sslrootcert | | N | SSL/TLS Root Cert |
| mysql_protocol | tcp | N | Connection protocol |
| mysql_socket | | N | Unix socket path |
| mysql_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |


Finally, placeholders for mysql differ from those of postgres, changing from $1, $2, etc., to simply ?. These are some **example** queries for `mysql`:
Expand All @@ -674,6 +682,11 @@ Acl query:
SELECT topic FROM acl WHERE (username = ?) AND rw = ?
```

**DB connect tries**: on startup, depending on `mysql_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
but you may choose to ping a max amount of times by setting any positive number.
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
Expand Down Expand Up @@ -720,12 +733,13 @@ ON UPDATE CASCADE
The `sqlite` backend works in the same way as `postgres` and `mysql` do, except that being a light weight db, it has fewer configuration options.
The following `auth_opt_` options are supported:

| Option | default | Mandatory | Meaning |
| --------------------- | ----------------- | :---------: | ------------------------ |
| sqlite_source | | Y | SQLite3 source
| sqlite_userquery | | Y | SQL for users
| sqlite_superquery | | N | SQL for superusers
| sqlite_aclquery | | N | SQL for ACLs
| Option | default | Mandatory | Meaning |
| ------------------------- | ----------------- | :---------: | ----------------------------------------------------------- |
| sqlite_source | | Y | SQLite3 source |
| sqlite_userquery | | Y | SQL for users |
| sqlite_superquery | | N | SQL for superusers |
| sqlite_aclquery | | N | SQL for ACLs |
| sqlite_connect_tries | -1 | N | x < 0: try forever, x > 0: try x times |

SQLite3 allows to connect to an in-memory db, or a single file one, so source maybe `memory` (not :memory:) or the path to a file db.

Expand All @@ -745,6 +759,11 @@ sqlite_superquery SELECT COUNT(*) FROM account WHERE username = ? AND super = 1
sqlite_aclquery SELECT topic FROM acl WHERE (username = ?) AND rw >= ?
```

**DB connect tries**: on startup, depending on `sqlite_connect_tries` option, the plugin will try to connect and ping the DB a max number of times or forever every 2 seconds.
By default it will try to reconnect forever to maintain backwards compatibility and avoid issues when `mosquitto` starts before the DB service does,
but you may choose to ping a max amount of times by setting any positive number.
If given 0, the DB will try to connect only once, which would be the same as setting the option to 1.

#### Password hashing

For instructions on how to set a backend specific hasher or use the general one, see [Hashing](#hashing).
Expand Down
20 changes: 17 additions & 3 deletions backends/db.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backends

import (
"fmt"
"time"

"github.com/jmoiron/sqlx"
Expand All @@ -11,20 +12,33 @@ import (
// OpenDatabase opens the database and performs a ping to make sure the
// database is up.
// Taken from brocaar's lora-app-server: https://github.com/brocaar/lora-app-server
func OpenDatabase(dsn, engine string) (*sqlx.DB, error) {
func OpenDatabase(dsn, engine string, tries int) (*sqlx.DB, error) {

db, err := sqlx.Open(engine, dsn)
if err != nil {
return nil, errors.Wrap(err, "database connection error")
}

for {
if tries == 0 {
tries = 1
}

for tries != 0 {
if err = db.Ping(); err != nil {
log.Errorf("ping database error, will retry in 2s: %s", err)
log.Errorf("ping database %s error, will retry in 2s: %s", engine, err)
time.Sleep(2 * time.Second)
} else {
break
}

if tries > 0 {
tries--
}
}

// Return last ping error when done trying.
if tries == 0 {
return nil, fmt.Errorf("couldn't ping database %s: %s", engine, err)
}

return db, nil
Expand Down
15 changes: 14 additions & 1 deletion backends/mysql.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"database/sql"
"fmt"
"io/ioutil"
"strconv"
"strings"

mq "github.com/go-sql-driver/mysql"
Expand Down Expand Up @@ -34,6 +35,8 @@ type Mysql struct {
SocketPath string
AllowNativePasswords bool
hasher hashing.HashComparer

connectTries int
}

func NewMysql(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Mysql, error) {
Expand Down Expand Up @@ -190,8 +193,18 @@ func NewMysql(authOpts map[string]string, logLevel log.Level, hasher hashing.Has
}
}

if tries, ok := authOpts["mysql_connect_tries"]; ok {
connectTries, err := strconv.Atoi(tries)

if err != nil {
log.Warnf("invalid mysql connect tries options: %s", err)
} else {
mysql.connectTries = connectTries
}
}

var err error
mysql.DB, err = OpenDatabase(msConfig.FormatDSN(), "mysql")
mysql.DB, err = OpenDatabase(msConfig.FormatDSN(), "mysql", mysql.connectTries)

if err != nil {
return mysql, errors.Errorf("MySql backend error: couldn't open db: %s", err)
Expand Down
16 changes: 15 additions & 1 deletion backends/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package backends
import (
"database/sql"
"fmt"
"strconv"
"strings"

"github.com/iegomez/mosquitto-go-auth/hashing"
Expand All @@ -28,6 +29,8 @@ type Postgres struct {
SSLKey string
SSLRootCert string
hasher hashing.HashComparer

connectTries int
}

func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Postgres, error) {
Expand All @@ -46,6 +49,7 @@ func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.
SuperuserQuery: "",
AclQuery: "",
hasher: hasher,
connectTries: -1,
}

if host, ok := authOpts["pg_host"]; ok {
Expand Down Expand Up @@ -134,8 +138,18 @@ func NewPostgres(authOpts map[string]string, logLevel log.Level, hasher hashing.
connStr = fmt.Sprintf("%s sslmode=disable", connStr)
}

if tries, ok := authOpts["pg_connect_tries"]; ok {
connectTries, err := strconv.Atoi(tries)

if err != nil {
log.Warnf("invalid postgres connect tries options: %s", err)
} else {
postgres.connectTries = connectTries
}
}

var err error
postgres.DB, err = OpenDatabase(connStr, "postgres")
postgres.DB, err = OpenDatabase(connStr, "postgres", postgres.connectTries)

if err != nil {
return postgres, errors.Errorf("PG backend error: couldn't open db: %s", err)
Expand Down
15 changes: 14 additions & 1 deletion backends/sqlite.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package backends

import (
"database/sql"
"strconv"
"strings"

"github.com/iegomez/mosquitto-go-auth/hashing"
Expand All @@ -19,6 +20,8 @@ type Sqlite struct {
SuperuserQuery string
AclQuery string
hasher hashing.HashComparer

connectTries int
}

func NewSqlite(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Sqlite, error) {
Expand Down Expand Up @@ -69,8 +72,18 @@ func NewSqlite(authOpts map[string]string, logLevel log.Level, hasher hashing.Ha
connStr = sqlite.Source
}

if tries, ok := authOpts["sqlite_connect_tries"]; ok {
connectTries, err := strconv.Atoi(tries)

if err != nil {
log.Warnf("invalid sqlite connect tries options: %s", err)
} else {
sqlite.connectTries = connectTries
}
}

var err error
sqlite.DB, err = OpenDatabase(connStr, "sqlite3")
sqlite.DB, err = OpenDatabase(connStr, "sqlite3", sqlite.connectTries)

if err != nil {
return sqlite, errors.Errorf("sqlite backend error: couldn't open db %s: %s", connStr, err)
Expand Down

0 comments on commit f0eeb85

Please sign in to comment.