From eaa72550aa42228e4aa21661af175c96781fd280 Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Wed, 8 Oct 2025 11:49:16 -0700 Subject: [PATCH 1/2] Setup exporter to conditionally compile for godror Signed-off-by: Anders Swanson --- collector/collector.go | 1 - collector/config.go | 3 +- collector/database.go | 125 ----------------------------------------- collector/types.go | 3 +- main.go | 3 +- 5 files changed, 3 insertions(+), 132 deletions(-) diff --git a/collector/collector.go b/collector/collector.go index 5bb6b6ef..7f3e1e46 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -20,7 +20,6 @@ import ( "sync" "time" - _ "github.com/godror/godror" "github.com/prometheus/client_golang/prometheus" ) diff --git a/collector/config.go b/collector/config.go index 8a665399..c7f93f75 100644 --- a/collector/config.go +++ b/collector/config.go @@ -5,7 +5,6 @@ package collector import ( "fmt" - "github.com/godror/godror/dsn" "github.com/oracle/oracle-db-appdev-monitoring/azvault" "github.com/oracle/oracle-db-appdev-monitoring/ocivault" "github.com/prometheus/exporter-toolkit/web" @@ -42,7 +41,7 @@ type DatabaseConfig struct { } type ConnectConfig struct { - Role dsn.AdminRole + Role string TNSAdmin string `yaml:"tnsAdmin"` ExternalAuth bool `yaml:"externalAuth"` MaxOpenConns *int `yaml:"maxOpenConns"` diff --git a/collector/database.go b/collector/database.go index 7b2cde7a..0478ec1e 100644 --- a/collector/database.go +++ b/collector/database.go @@ -6,13 +6,8 @@ package collector import ( "context" "database/sql" - "errors" - "fmt" - "github.com/godror/godror" - "github.com/godror/godror/dsn" "github.com/prometheus/client_golang/prometheus" "log/slog" - "strings" "time" ) @@ -34,28 +29,6 @@ func (d *Database) UpMetric(exporterLabels map[string]string) prometheus.Metric ) } -// ping the database. If the database is disconnected, try to reconnect. -// If the database type is unknown, try to reload it. -func (d *Database) ping(logger *slog.Logger) error { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - err := d.Session.PingContext(ctx) - if err != nil { - d.Up = 0 - if isInvalidCredentialsError(err) { - d.invalidate() - return err - } - // If database is closed, try to reconnect - if strings.Contains(err.Error(), "sql: database is closed") { - d.Session = connect(logger, d.Name, d.Config) - } - return err - } - d.Up = 1 - return nil -} - func (d *Database) constLabels(labels map[string]string) map[string]string { labels["database"] = d.Name @@ -125,101 +98,3 @@ func (d *Database) IsValid() bool { func (d *Database) invalidate() { d.Valid = false } - -func isInvalidCredentialsError(err error) bool { - err = errors.Unwrap(err) - if err == nil { - return false - } - oraErr, ok := err.(*godror.OraErr) - if !ok { - return false - } - return oraErr.Code() == ora01017code || oraErr.Code() == ora28000code -} - -func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *sql.DB { - logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname) - - var P godror.ConnectionParams - password := dbconfig.GetPassword() - username := dbconfig.GetUsername() - // If password is not specified, externalAuth will be true, and we'll ignore user input - dbconfig.ExternalAuth = password == "" - logger.Debug(fmt.Sprintf("external authentication set to %t", dbconfig.ExternalAuth), "database", dbname) - msg := "Using Username/Password Authentication." - if dbconfig.ExternalAuth { - msg = "Database Password not specified; will attempt to use external authentication (ignoring user input)." - dbconfig.Username = "" - } - logger.Info(msg, "database", dbname) - externalAuth := sql.NullBool{ - Bool: dbconfig.ExternalAuth, - Valid: true, - } - P.Username, P.Password, P.ConnectString, P.ExternalAuth = username, godror.NewPassword(password), dbconfig.URL, externalAuth - - if dbconfig.GetPoolIncrement() > 0 { - logger.Debug(fmt.Sprintf("set pool increment to %d", dbconfig.PoolIncrement), "database", dbname) - P.PoolParams.SessionIncrement = dbconfig.GetPoolIncrement() - } - if dbconfig.GetPoolMaxConnections() > 0 { - logger.Debug(fmt.Sprintf("set pool max connections to %d", dbconfig.PoolMaxConnections), "database", dbname) - P.PoolParams.MaxSessions = dbconfig.GetPoolMaxConnections() - } - if dbconfig.GetPoolMinConnections() > 0 { - logger.Debug(fmt.Sprintf("set pool min connections to %d", dbconfig.PoolMinConnections), "database", dbname) - P.PoolParams.MinSessions = dbconfig.GetPoolMinConnections() - } - - P.PoolParams.WaitTimeout = time.Second * 5 - - // if TNS_ADMIN env var is set, set ConfigDir to that location - P.ConfigDir = dbconfig.TNSAdmin - - switch dbconfig.Role { - case "SYSDBA": - P.AdminRole = dsn.SysDBA - case "SYSOPER": - P.AdminRole = dsn.SysOPER - case "SYSBACKUP": - P.AdminRole = dsn.SysBACKUP - case "SYSDG": - P.AdminRole = dsn.SysDG - case "SYSKM": - P.AdminRole = dsn.SysKM - case "SYSRAC": - P.AdminRole = dsn.SysRAC - case "SYSASM": - P.AdminRole = dsn.SysASM - default: - P.AdminRole = dsn.NoRole - } - - // note that this just configures the connection, it does not actually connect until later - // when we call db.Ping() - db := sql.OpenDB(godror.NewConnector(P)) - logger.Debug(fmt.Sprintf("set max idle connections to %d", dbconfig.MaxIdleConns), "database", dbname) - db.SetMaxIdleConns(dbconfig.GetMaxIdleConns()) - logger.Debug(fmt.Sprintf("set max open connections to %d", dbconfig.MaxOpenConns), "database", dbname) - db.SetMaxOpenConns(dbconfig.GetMaxOpenConns()) - db.SetConnMaxLifetime(0) - logger.Debug(fmt.Sprintf("Successfully configured connection to %s", maskDsn(dbconfig.URL)), "database", dbname) - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - if _, err := db.ExecContext(ctx, ` - begin - dbms_application_info.set_client_info('oracledb_exporter'); - end;`); err != nil { - logger.Info("Could not set CLIENT_INFO.", "database", dbname) - } - - var sysdba string - if err := db.QueryRowContext(ctx, "select sys_context('USERENV', 'ISDBA') from dual").Scan(&sysdba); err != nil { - logger.Error("error checking my database role", "error", err, "database", dbname) - } - logger.Info("Connected as SYSDBA? "+sysdba, "database", dbname) - - return db -} diff --git a/collector/types.go b/collector/types.go index b86f2259..c268e58c 100644 --- a/collector/types.go +++ b/collector/types.go @@ -4,7 +4,6 @@ package collector import ( "database/sql" - "github.com/godror/godror/dsn" "github.com/prometheus/client_golang/prometheus" "log/slog" "sync" @@ -59,7 +58,7 @@ type Config struct { User string Password string ConnectString string - DbRole dsn.AdminRole + DbRole string ConfigDir string ExternalAuth bool MaxIdleConns int diff --git a/main.go b/main.go index 570bc1b2..33abee1a 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,6 @@ import ( "github.com/prometheus/common/promslog" "github.com/prometheus/common/promslog/flag" - "github.com/godror/godror/dsn" "github.com/prometheus/client_golang/prometheus" cversion "github.com/prometheus/client_golang/prometheus/collectors/version" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -87,7 +86,7 @@ func main() { User: user, Password: password, ConnectString: connectString, - DbRole: dsn.AdminRole(dbrole), + DbRole: dbrole, ConfigDir: tnsadmin, ExternalAuth: externalAuth, MaxOpenConns: *maxOpenConns, From c26aaa9e5b9061abd4ba927cc347e264532ebf5e Mon Sep 17 00:00:00 2001 From: Anders Swanson Date: Wed, 8 Oct 2025 11:50:48 -0700 Subject: [PATCH 2/2] Setup exporter to conditionally compile for godror Signed-off-by: Anders Swanson --- collector/connect_godror.go | 138 ++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 collector/connect_godror.go diff --git a/collector/connect_godror.go b/collector/connect_godror.go new file mode 100644 index 00000000..dc8e5029 --- /dev/null +++ b/collector/connect_godror.go @@ -0,0 +1,138 @@ +// Copyright (c) 2025, Oracle and/or its affiliates. +// Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. + +//go:build !goora + +package collector + +import ( + "context" + "database/sql" + "errors" + "fmt" + "github.com/godror/godror" + "github.com/godror/godror/dsn" + "log/slog" + "strings" + "time" +) + +func connect(logger *slog.Logger, dbname string, dbconfig DatabaseConfig) *sql.DB { + logger.Debug("Launching connection to "+maskDsn(dbconfig.URL), "database", dbname) + + var P godror.ConnectionParams + password := dbconfig.GetPassword() + username := dbconfig.GetUsername() + // If password is not specified, externalAuth will be true, and we'll ignore user input + dbconfig.ExternalAuth = password == "" + logger.Debug(fmt.Sprintf("external authentication set to %t", dbconfig.ExternalAuth), "database", dbname) + msg := "Using Username/Password Authentication." + if dbconfig.ExternalAuth { + msg = "Database Password not specified; will attempt to use external authentication (ignoring user input)." + dbconfig.Username = "" + } + logger.Info(msg, "database", dbname) + externalAuth := sql.NullBool{ + Bool: dbconfig.ExternalAuth, + Valid: true, + } + P.Username, P.Password, P.ConnectString, P.ExternalAuth = username, godror.NewPassword(password), dbconfig.URL, externalAuth + + if dbconfig.GetPoolIncrement() > 0 { + logger.Debug(fmt.Sprintf("set pool increment to %d", dbconfig.PoolIncrement), "database", dbname) + P.PoolParams.SessionIncrement = dbconfig.GetPoolIncrement() + } + if dbconfig.GetPoolMaxConnections() > 0 { + logger.Debug(fmt.Sprintf("set pool max connections to %d", dbconfig.PoolMaxConnections), "database", dbname) + P.PoolParams.MaxSessions = dbconfig.GetPoolMaxConnections() + } + if dbconfig.GetPoolMinConnections() > 0 { + logger.Debug(fmt.Sprintf("set pool min connections to %d", dbconfig.PoolMinConnections), "database", dbname) + P.PoolParams.MinSessions = dbconfig.GetPoolMinConnections() + } + + P.PoolParams.WaitTimeout = time.Second * 5 + + // if TNS_ADMIN env var is set, set ConfigDir to that location + P.ConfigDir = dbconfig.TNSAdmin + + switch dbconfig.Role { + case "SYSDBA": + P.AdminRole = dsn.SysDBA + case "SYSOPER": + P.AdminRole = dsn.SysOPER + case "SYSBACKUP": + P.AdminRole = dsn.SysBACKUP + case "SYSDG": + P.AdminRole = dsn.SysDG + case "SYSKM": + P.AdminRole = dsn.SysKM + case "SYSRAC": + P.AdminRole = dsn.SysRAC + case "SYSASM": + P.AdminRole = dsn.SysASM + default: + P.AdminRole = dsn.NoRole + } + + // note that this just configures the connection, it does not actually connect until later + // when we call db.Ping() + db := sql.OpenDB(godror.NewConnector(P)) + logger.Debug(fmt.Sprintf("set max idle connections to %d", dbconfig.MaxIdleConns), "database", dbname) + db.SetMaxIdleConns(dbconfig.GetMaxIdleConns()) + logger.Debug(fmt.Sprintf("set max open connections to %d", dbconfig.MaxOpenConns), "database", dbname) + db.SetMaxOpenConns(dbconfig.GetMaxOpenConns()) + db.SetConnMaxLifetime(0) + logger.Debug(fmt.Sprintf("Successfully configured connection to %s", maskDsn(dbconfig.URL)), "database", dbname) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + if _, err := db.ExecContext(ctx, ` + begin + dbms_application_info.set_client_info('oracledb_exporter'); + end;`); err != nil { + logger.Info("Could not set CLIENT_INFO.", "database", dbname) + } + + var sysdba string + if err := db.QueryRowContext(ctx, "select sys_context('USERENV', 'ISDBA') from dual").Scan(&sysdba); err != nil { + logger.Error("error checking my database role", "error", err, "database", dbname) + } + logger.Info("Connected as SYSDBA? "+sysdba, "database", dbname) + + return db +} + +// ping the database. If the database is disconnected, try to reconnect. +// If the database type is unknown, try to reload it. +func (d *Database) ping(logger *slog.Logger) error { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err := d.Session.PingContext(ctx) + if err != nil { + d.Up = 0 + if isInvalidCredentialsError(err) { + d.invalidate() + return err + } + // If database is closed, try to reconnect + if strings.Contains(err.Error(), "sql: database is closed") { + d.Session = connect(logger, d.Name, d.Config) + } + return err + } + d.Up = 1 + return nil +} + +func isInvalidCredentialsError(err error) bool { + err = errors.Unwrap(err) + if err == nil { + return false + } + oraErr, ok := err.(*godror.OraErr) + if !ok { + return false + } + return oraErr.Code() == ora01017code || oraErr.Code() == ora28000code +}