Implement the compileable source code of the basic integration test suite.

This is the first step in moving the integration test suite to a more
reliable Golang based one.
This commit is contained in:
Will Rouesnel 2016-11-17 03:09:20 +11:00
parent a95fad8d1e
commit 045ae96430
4 changed files with 118 additions and 22 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
.build .build
postgres_exporter postgres_exporter
postgres_exporter_integration_test
*.tar.gz *.tar.gz
*.test *.test
*-stamp *-stamp

View File

@ -9,6 +9,10 @@ all: vet test postgres_exporter
postgres_exporter: $(GO_SRC) postgres_exporter: $(GO_SRC)
CGO_ENABLED=0 go build -a -ldflags "-extldflags '-static' -X main.Version=git:$(shell git rev-parse HEAD)" -o postgres_exporter . CGO_ENABLED=0 go build -a -ldflags "-extldflags '-static' -X main.Version=git:$(shell git rev-parse HEAD)" -o postgres_exporter .
postgres_exporter_integration_test: $(GO_SRC)
CGO_ENABLED=0 go test -c -tags integration \
-a -ldflags "-extldflags '-static' -X main.Version=git:$(shell git rev-parse HEAD)" -o postgres_exporter_integration_test .
# Take a go build and turn it into a minimal container # Take a go build and turn it into a minimal container
docker: postgres_exporter docker: postgres_exporter
docker build -t $(CONTAINER_NAME) . docker build -t $(CONTAINER_NAME) .
@ -19,8 +23,8 @@ vet:
test: test:
go test -v . go test -v .
test-integration: postgres_exporter test-integration: postgres_exporter postgres_exporter_integration_test
tests/test-smoke ./postgres_exporter tests/test-smoke ./postgres_exporter ./postgres_exporter_integration_test
# Do a self-contained docker build - we pull the official upstream container # Do a self-contained docker build - we pull the official upstream container
# and do a self-contained build. # and do a self-contained build.

View File

@ -592,9 +592,11 @@ func newDesc(subsystem, name, help string) *prometheus.Desc {
// Query the SHOW variables from the query map // Query the SHOW variables from the query map
// TODO: make this more functional // TODO: make this more functional
func (e *Exporter) queryShowVariables(ch chan<- prometheus.Metric, db *sql.DB) { func queryShowVariables(ch chan<- prometheus.Metric, db *sql.DB, variableMap map[string]MetricMapNamespace) []error {
log.Debugln("Querying SHOW variables") log.Debugln("Querying SHOW variables")
for _, mapping := range e.variableMap { nonFatalErrors := []error{}
for _, mapping := range variableMap {
for columnName, columnMapping := range mapping.columnMappings { for columnName, columnMapping := range mapping.columnMappings {
// Check for a discard request on this value // Check for a discard request on this value
if columnMapping.discard { if columnMapping.discard {
@ -607,23 +609,26 @@ func (e *Exporter) queryShowVariables(ch chan<- prometheus.Metric, db *sql.DB) {
var val interface{} var val interface{}
err := row.Scan(&val) err := row.Scan(&val)
if err != nil { if err != nil {
log.Errorln("Error scanning runtime variable:", columnName, err) nonFatalErrors = append(nonFatalErrors, errors.New(fmt.Sprintln("Error scanning runtime variable:", columnName, err)))
continue continue
} }
fval, ok := columnMapping.conversion(val) fval, ok := columnMapping.conversion(val)
if !ok { if !ok {
e.error.Set(1) nonFatalErrors = append(nonFatalErrors, errors.New(fmt.Sprintln("Unexpected error parsing column: ", namespace, columnName, val)))
log.Errorln("Unexpected error parsing column: ", namespace, columnName, val)
continue continue
} }
ch <- prometheus.MustNewConstMetric(columnMapping.desc, columnMapping.vtype, fval) ch <- prometheus.MustNewConstMetric(columnMapping.desc, columnMapping.vtype, fval)
} }
} }
return nonFatalErrors
} }
func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace string, mapping MetricMapNamespace) error { // Query within a namespace mapping and emit metrics. Returns fatal errors if
// the scrape fails, and a slice of errors if they were non-fatal.
func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace string, mapping MetricMapNamespace) ([]error, error) {
query, er := queryOverrides[namespace] query, er := queryOverrides[namespace]
if er == false { if er == false {
query = fmt.Sprintf("SELECT * FROM %s;", namespace) query = fmt.Sprintf("SELECT * FROM %s;", namespace)
@ -632,14 +637,14 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
// Don't fail on a bad scrape of one metric // Don't fail on a bad scrape of one metric
rows, err := db.Query(query) rows, err := db.Query(query)
if err != nil { if err != nil {
return errors.New(fmt.Sprintln("Error running query on database: ", namespace, err)) return []error{}, errors.New(fmt.Sprintln("Error running query on database: ", namespace, err))
} }
defer rows.Close() defer rows.Close()
var columnNames []string var columnNames []string
columnNames, err = rows.Columns() columnNames, err = rows.Columns()
if err != nil { if err != nil {
return errors.New(fmt.Sprintln("Error retrieving column list for: ", namespace, err)) return []error{}, errors.New(fmt.Sprintln("Error retrieving column list for: ", namespace, err))
} }
// Make a lookup map for the column indices // Make a lookup map for the column indices
@ -654,10 +659,12 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
scanArgs[i] = &columnData[i] scanArgs[i] = &columnData[i]
} }
nonfatalErrors := []error{}
for rows.Next() { for rows.Next() {
err = rows.Scan(scanArgs...) err = rows.Scan(scanArgs...)
if err != nil { if err != nil {
return errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err)) return []error{}, errors.New(fmt.Sprintln("Error retrieving rows:", namespace, err))
} }
// Get the label values for this row // Get the label values for this row
@ -678,7 +685,7 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
value, ok := dbToFloat64(columnData[idx]) value, ok := dbToFloat64(columnData[idx])
if !ok { if !ok {
log.Errorln("Unexpected error parsing column: ", namespace, columnName, columnData[idx]) nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("Unexpected error parsing column: ", namespace, columnName, columnData[idx])))
continue continue
} }
@ -692,7 +699,7 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
// unexpected anyway. // unexpected anyway.
value, ok := dbToFloat64(columnData[idx]) value, ok := dbToFloat64(columnData[idx])
if !ok { if !ok {
log.Warnln("Unparseable column type - discarding: ", namespace, columnName, err) nonfatalErrors = append(nonfatalErrors, errors.New(fmt.Sprintln("Unparseable column type - discarding: ", namespace, columnName, err)))
continue continue
} }
@ -700,7 +707,32 @@ func queryNamespaceMapping(ch chan<- prometheus.Metric, db *sql.DB, namespace st
} }
} }
} }
return nil return nonfatalErrors, nil
}
// Iterate through all the namespace mappings in the exporter and run their
// queries.
func queryNamespaceMappings(ch chan<- prometheus.Metric, db *sql.DB, metricMap map[string]MetricMapNamespace) map[string]error {
// Return a map of namespace -> errors
namespaceErrors := make(map[string]error)
for namespace, mapping := range metricMap {
log.Debugln("Querying namespace: ", namespace)
nonFatalErrors, err := queryNamespaceMapping(ch, db, namespace, mapping)
// Serious error - a namespace disappeard
if err != nil {
namespaceErrors[namespace] = err
log.Infoln(err)
}
// Non-serious errors - likely version or parsing problems.
if len(nonFatalErrors) > 0 {
for _, err := range nonFatalErrors {
log.Infoln(err.Error())
}
}
}
return namespaceErrors
} }
func (e *Exporter) scrape(ch chan<- prometheus.Metric) { func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
@ -734,15 +766,14 @@ func (e *Exporter) scrape(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(versionDesc, prometheus.UntypedValue, 1, versionString, shortVersion) ch <- prometheus.MustNewConstMetric(versionDesc, prometheus.UntypedValue, 1, versionString, shortVersion)
// Handle querying the show variables // Handle querying the show variables
e.queryShowVariables(ch, db) nonFatalErrors := queryShowVariables(ch, db, e.variableMap)
if len(nonFatalErrors) > 0 {
e.error.Set(1)
}
for namespace, mapping := range e.metricMap { errMap := queryNamespaceMappings(ch, db, e.metricMap)
log.Debugln("Querying namespace: ", namespace) if len(errMap) > 0 {
err = queryNamespaceMapping(ch, db, namespace, mapping) e.error.Set(1)
if err != nil {
log.Infoln(err)
e.error.Set(1)
}
} }
} }

View File

@ -0,0 +1,60 @@
// These are specialized integration tests. We only build them when we're doing
// a lot of additional work to keep the external docker environment they require
// working.
// +build integration
package main
import (
"os"
"testing"
. "gopkg.in/check.v1"
"github.com/prometheus/client_golang/prometheus"
"database/sql"
_ "github.com/lib/pq"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type IntegrationSuite struct{
e *Exporter
}
var _ = Suite(&IntegrationSuite{})
func (s *IntegrationSuite) SetUpSuite(c *C) {
dsn := os.Getenv("DATA_SOURCE_NAME")
c.Assert(dsn, Not(Equals), "")
exporter := NewExporter(dsn)
c.Assert(exporter, NotNil)
// Assign the exporter to the suite
s.e = exporter
prometheus.MustRegister(exporter)
}
func (s *IntegrationSuite) TestAllNamespacesReturnResults(c *C) {
// Setup a dummy channel to consume metrics
ch := make(chan prometheus.Metric, 100)
go func() {
for _ = range ch {}
}()
// Open a database connection
db, err := sql.Open("postgres", s.e.dsn)
c.Assert(db, NotNil)
c.Assert(err, IsNil)
defer db.Close()
// Check the show variables work
nonFatalErrors := queryShowVariables(ch, db, s.e.variableMap)
c.Check(len(nonFatalErrors), Equals, 0)
// This should never happen in our test cases.
errMap := queryNamespaceMappings(ch, db, s.e.metricMap)
c.Check(len(errMap), Equals, 0)
}