Skip to content

Commit 251b29a

Browse files
Invalidate database on failed login (#372)
* Invalid database on failed login Signed-off-by: Anders Swanson <[email protected]>
1 parent f2e810a commit 251b29a

File tree

6 files changed

+45
-2
lines changed

6 files changed

+45
-2
lines changed

alertlog/alertlog.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ type LogRecord struct {
2424
var databaseFailures map[string]int = map[string]int{}
2525

2626
func UpdateLog(logDestination string, logger *slog.Logger, d *collector.Database) {
27+
// Do not try to query the alert log if the database configuration is invalid.
28+
if !d.IsValid() {
29+
return
30+
}
2731

2832
queryFailures := databaseFailures[d.Name]
2933
if queryFailures == 3 {

collector/collector.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ func (e *Exporter) scheduledScrape(tick *time.Time) {
237237
}
238238

239239
func (e *Exporter) scrapeDatabase(ch chan<- prometheus.Metric, errChan chan<- error, d *Database, tick *time.Time) int {
240+
// If the database configuration is invalid, do not attempt to ping or reestablish the database connection.
241+
if !d.IsValid() {
242+
e.logger.Warn("Invalid database configuration, will not attempt reconnection", "database", d.Name)
243+
errChan <- fmt.Errorf("database %s is invalid, will not be scraped", d.Name)
244+
return 1
245+
}
240246
// If ping fails, we will try again on the next iteration of metrics scraping
241247
if err := d.ping(e.logger); err != nil {
242248
e.logger.Error("Error pinging database", "error", err, "database", d.Name)

collector/database.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package collector
66
import (
77
"context"
88
"database/sql"
9+
"errors"
910
"fmt"
1011
"github.com/godror/godror"
1112
"github.com/godror/godror/dsn"
@@ -15,6 +16,10 @@ import (
1516
"time"
1617
)
1718

19+
const (
20+
ora01017code = 1017
21+
)
22+
1823
func (d *Database) UpMetric(exporterLabels map[string]string) prometheus.Metric {
1924
desc := prometheus.NewDesc(
2025
prometheus.BuildFQName(namespace, "", "up"),
@@ -49,6 +54,10 @@ func (d *Database) ping(logger *slog.Logger) error {
4954
err := d.Session.PingContext(ctx)
5055
if err != nil {
5156
d.Up = 0
57+
if isInvalidCredentialsError(err) {
58+
d.invalidate()
59+
return err
60+
}
5261
// If database is closed, try to reconnect
5362
if strings.Contains(err.Error(), "sql: database is closed") {
5463
db, dbtype := connect(logger, d.Name, d.Config)
@@ -83,6 +92,7 @@ func NewDatabase(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *D
8392
Session: db,
8493
Type: dbtype,
8594
Config: dbconfig,
95+
Valid: true,
8696
}
8797
}
8898

@@ -127,6 +137,26 @@ func (d *Database) WarmupConnectionPool(logger *slog.Logger) {
127137
}
128138
}
129139

140+
func (d *Database) IsValid() bool {
141+
return d.Valid
142+
}
143+
144+
func (d *Database) invalidate() {
145+
d.Valid = false
146+
}
147+
148+
func isInvalidCredentialsError(err error) bool {
149+
err = errors.Unwrap(err)
150+
if err == nil {
151+
return false
152+
}
153+
oraErr, ok := err.(*godror.OraErr)
154+
if !ok {
155+
return false
156+
}
157+
return oraErr.Code() == ora01017
158+
}
159+
130160
func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) (*sql.DB, float64) {
131161
logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname)
132162

collector/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ type Database struct {
3434
// MetricsCache holds computed metrics for a database, so these metrics are available on each scrape.
3535
// Given a metric's scrape configuration, it may not be computed on the same interval as other metrics.
3636
MetricsCache *MetricsCache
37+
38+
Valid bool
3739
}
3840

3941
type MetricsCache struct {

site/docs/releases/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Our current priorities to support metrics for advanced database features and use
1313

1414
- Updated project dependencies.
1515
- Standardize multi-arch builds and document supported database versions.
16+
- If the exporter fails to connect to a database due to invalid credentials (ORA-01017 error), that database configuration will be invalidated and the exporter will not attempt to re-establish the database connection. Other databases will continue to be scraped.
1617
- Metrics with an empty databases array (`databases = []`) are now considered disabled, and will not be scraped.
1718
- Increased the default query timeout for the `top_sql` metric to 10 seconds (previously 5 seconds).
1819
- Metrics using the `scrapeinterval` property will no longer be scraped on every request if they have a cached value. This only applies when the metrics exporter is configured to scrape metrics _on request_, rather than on a global interval.

site/docs/releases/roadmap.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ We welcome input on community-driven features you'd like to see supported. Pleas
1212
Currently, we plan to address the following key features:
1313

1414
- Provide default Oracle Exadata metrics
15-
- Implement connection storm protection: prevent the exporter from repeatedly connecting when the credentials fail, to prevent a storm of connections causing accounts to be locked across a large number of databases
15+
- Provide default GoldenGate metrics
16+
- Enhance database alert logging and alert log metrics
1617
- Provide the option to have the Oracle client outside of the container image, e.g., on a shared volume,
1718
- Implement the ability to update the configuration dynamically, i.e., without a restart
1819
- Implement support for tracing within the database, e.g., using an execution context ID provide by an external caller
1920
- Provide additional pre-built Grafana dashboards,
2021
- Integration with Spring Observability, e.g., Micrometer
21-
- Provide additional documentation and samples

0 commit comments

Comments
 (0)