From 938f437dd4cf30ed6481fa6ad322a9dfc8c9644a Mon Sep 17 00:00:00 2001 From: Alexandre NICOLAIE Date: Tue, 4 May 2021 10:08:06 +0200 Subject: [PATCH 1/4] refact(flags): add more flexibility for logging --- 389ds_exporter.go | 31 ++++++++++++++++++++----------- README.md | 4 ++-- go.mod | 1 + go.sum | 8 +++++++- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/389ds_exporter.go b/389ds_exporter.go index 784688e..9e5d599 100644 --- a/389ds_exporter.go +++ b/389ds_exporter.go @@ -12,32 +12,41 @@ import ( ) var ( - listenPort = flag.String("web.listen-address", ":9496", "Bind address for prometheus HTTP metrics server") - metricsPath = flag.String("web.telemetry-path", "/metrics", "Path to expose metrics on") + interval = flag.Duration("interval", 60*time.Second, "Scrape interval") + ipaDomain = flag.String("ipa-domain", "", "FreeIPA domain e.g. example.org") ldapAddr = flag.String("ldap.addr", "localhost:389", "Address of 389ds server") ldapUser = flag.String("ldap.user", "cn=Directory Manager", "389ds Directory Manager user") ldapPass = flag.String("ldap.pass", "", "389ds Directory Manager password") - ipaDomain = flag.String("ipa-domain", "", "FreeIPA domain e.g. example.org") - interval = flag.Duration("interval", 60*time.Second, "Scrape interval") - debug = flag.Bool("debug", false, "Debug logging") - jsonFormat = flag.Bool("log-json", false, "JSON formatted log messages") + logLevel = flag.String("log.level", "info", "Log level") + logFormat = flag.String("log.format", "default", "Log format (default, json)") + listenPort = flag.String("web.listen-address", ":9496", "Bind address for prometheus HTTP metrics server") + metricsPath = flag.String("web.telemetry-path", "/metrics", "Path to expose metrics on") + showVersion = flag.Bool("version", false, "Exporter version") ) func main() { flag.Parse() - if *debug { - log.SetLevel(log.DebugLevel) + if level, err := log.ParseLevel(*logLevel); err != nil { + log.Fatalf("log.level must be one of %v", log.AllLevels) + } else { + log.SetLevel(level) } - if *jsonFormat { + + switch *logFormat { + case "default": + log.SetFormatter(&log.TextFormatter{}) + case "json": log.SetFormatter(&log.JSONFormatter{}) + default: + log.Fatal("log.level must be one of [default json]") } if *ldapPass == "" { - log.Fatal("ldapPass cannot be empty") + log.Fatal("ldap.pass cannot be empty") } if *ipaDomain == "" { - log.Fatal("ipaDomain cannot be empty") + log.Fatal("ipa-domain cannot be empty") } log.Info("Starting prometheus HTTP metrics server on ", *listenPort) diff --git a/README.md b/README.md index a0bbcfd..e6b3a52 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,13 @@ To run: ./389ds_exporter --help ``` -* __`-debug`:__ Debug logging * __`-interval`:__ Scrape interval (default 1m0s) * __`-ipa-domain`:__ FreeIPA domain e.g. example.org * __`-ldap.addr`:__ Address of 389ds server (default "localhost:389") * __`-ldap.pass`:__ 389ds Directory Manager password * __`-ldap.user`:__ 389ds Directory Manager user (default "cn=Directory Manager") -* __`-log-json`:__ JSON formatted log messages +* __`-log.format`:__ Log format (default or json) +* __`-log.level`:__ Log level * __`-web.listen-address`:__ Bind address for prometheus HTTP metrics server (default ":9496") * __`-web.telemetry-path`:__ Path to expose metrics on (default "/metrics") diff --git a/go.mod b/go.mod index a0b7923..4ae8e37 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect github.com/sirupsen/logrus v1.2.1-0.20181103062819-44067abb194b + github.com/stretchr/testify v1.4.0 // indirect gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 // indirect gopkg.in/ldap.v2 v2.5.1 ) diff --git a/go.sum b/go.sum index b417c7f..5f8bcd1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -28,9 +29,11 @@ github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFd github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/sirupsen/logrus v1.2.1-0.20181103062819-44067abb194b h1:Iu2MGCAyUfeEfu+uSNNohhVGYJ1agVsRxLhyykae0H8= github.com/sirupsen/logrus v1.2.1-0.20181103062819-44067abb194b/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -68,5 +71,8 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225 h1:JBwmEvLfCqgPcIq8MjVMQxsF3LVL4XG/HH0qiG0+IFY= gopkg.in/asn1-ber.v1 v1.0.0-20170511165959-379148ca0225/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ldap.v2 v2.5.1 h1:wiu0okdNfjlBzg6UWvd1Hn8Y+Ux17/u/4nlk4CQr6tU= gopkg.in/ldap.v2 v2.5.1/go.mod h1:oI0cpe/D7HRtBQl8aTg+ZmzFUAvu4lsv3eLXMLGFxWk= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From ef079075ad01a1d2ff5bf2adeaf1b9cdc2a0dbd5 Mon Sep 17 00:00:00 2001 From: Alexandre NICOLAIE Date: Tue, 4 May 2021 12:14:22 +0200 Subject: [PATCH 2/4] refact(flags): add version management --- .github/workflows/golang-release.yaml | 5 ++++- 389ds_exporter.go | 6 ++++++ README.md | 2 +- VERSION | 1 - 4 files changed, 11 insertions(+), 3 deletions(-) delete mode 100644 VERSION diff --git a/.github/workflows/golang-release.yaml b/.github/workflows/golang-release.yaml index cda7404..05f12e1 100644 --- a/.github/workflows/golang-release.yaml +++ b/.github/workflows/golang-release.yaml @@ -13,7 +13,10 @@ jobs: with: go-version: 1.16.x - name: Build project - run: go build -o 389ds_exporter -v . + run: | + go build \ + -ldflags "-X github.com/prometheus/common/version.Version=${GITHUB_REF#refs/*/} -X github.com/prometheus/common/version.Branch=master -X github.com/prometheus/common/version.Revision=${{github.sha}} -X github.com/prometheus/common/version.BuildUser=github-action -X github.com/prometheus/common/version.BuildDate=$(date +'%Y%M%d-%H:%M:%S')" \ + -o 389ds_exporter -v . - name: Release binary uses: softprops/action-gh-release@v1 env: diff --git a/389ds_exporter.go b/389ds_exporter.go index 9e5d599..81da75e 100644 --- a/389ds_exporter.go +++ b/389ds_exporter.go @@ -2,6 +2,7 @@ package main import ( "flag" + "fmt" "net/http" "time" @@ -27,6 +28,11 @@ var ( func main() { flag.Parse() + if *showVersion { + fmt.Println(version.Print("Version")) + return + } + if level, err := log.ParseLevel(*logLevel); err != nil { log.Fatalf("log.level must be one of %v", log.AllLevels) } else { diff --git a/README.md b/README.md index e6b3a52..b341422 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ To run: * __`-ldap.user`:__ 389ds Directory Manager user (default "cn=Directory Manager") * __`-log.format`:__ Log format (default or json) * __`-log.level`:__ Log level +* __`version`:__ Show exporter version * __`-web.listen-address`:__ Bind address for prometheus HTTP metrics server (default ":9496") * __`-web.telemetry-path`:__ Path to expose metrics on (default "/metrics") @@ -47,7 +48,6 @@ To run: | ldap_389ds_scrape_count | Number of successful or unsuccessful scrapes | result | | ldap_389ds_scrape_duration_seconds | How long the last scrape took | - ### Credits This repo essentially started off as a clone of the openldap_exporter modified to query diff --git a/VERSION b/VERSION deleted file mode 100644 index d917d3e..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.1.2 From 9c400bd6ba045b10e5d2a6178cbb0a21b2fc55e0 Mon Sep 17 00:00:00 2001 From: Alexandre NICOLAIE Date: Tue, 4 May 2021 12:28:11 +0200 Subject: [PATCH 3/4] fix(metrics): set 0 to ldap_389ds_replication_status on status lost Signed-off-by: Alexandre NICOLAIE --- exporter/ldap.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/exporter/ldap.go b/exporter/ldap.go index 5917563..2db34e6 100644 --- a/exporter/ldap.go +++ b/exporter/ldap.go @@ -105,6 +105,10 @@ var ( ) ) +var ( + lastReplicaHostStatus = map[string]struct{}{} +) + func init() { prometheus.MustRegister( usersGauge, @@ -282,11 +286,11 @@ func ldapCountQuery(l *ldap.Conn, baseDN, searchFilter, attr string, scope int) } func ldapReplicationQuery(l *ldap.Conn, suffix string) error { - escaped_suffix := ldap_escaper.Replace(suffix) - base_dn := fmt.Sprintf("cn=replica,cn=%s,cn=mapping tree,cn=config", escaped_suffix) + escapedSuffix := ldap_escaper.Replace(suffix) + baseDn := fmt.Sprintf("cn=replica,cn=%s,cn=mapping tree,cn=config", escapedSuffix) req := ldap.NewSearchRequest( - base_dn, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, + baseDn, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, "(objectClass=nsds5replicationagreement)", []string{"nsDS5ReplicaHost", "nsds5replicaLastUpdateStatus"}, nil, ) sr, err := l.Search(req) @@ -294,9 +298,11 @@ func ldapReplicationQuery(l *ldap.Conn, suffix string) error { return err } + currentReplicaHostStatus := map[string]struct{}{} for _, entry := range sr.Entries { host := entry.GetAttributeValue("nsDS5ReplicaHost") status := entry.GetAttributeValue("nsds5replicaLastUpdateStatus") + if strings.Contains(status, "Incremental update succeeded") { // Error (0) Replica acquired successfully: Incremental update succeeded replicationStatusGauge.WithLabelValues(host).Set(1) } else if strings.Contains(status, "Problem connecting to replica") { // Error (-1) Problem connecting to replica - LDAP error: Can't contact LDAP server (connection error) @@ -308,7 +314,17 @@ func ldapReplicationQuery(l *ldap.Conn, suffix string) error { log.Warnf("Unknown replication status host: %s, status: %s", host, status) replicationStatusGauge.WithLabelValues(host).Set(0) } + + currentReplicaHostStatus[host] = struct{}{} + delete(lastReplicaHostStatus, host) + } + + // NOTE: reset replicationStatusGauge when we lost a replication agreement + for host := range lastReplicaHostStatus { + log.Warnf("Replication status lost for host '%s'", host) + replicationStatusGauge.WithLabelValues(host).Set(0) } + lastReplicaHostStatus = currentReplicaHostStatus return nil } From 90ddcb716c3c7d2862a5659ab64d631fe5f29d19 Mon Sep 17 00:00:00 2001 From: Alexandre NICOLAIE Date: Tue, 4 May 2021 15:15:23 +0200 Subject: [PATCH 4/4] refact(metrics): replace server label with destination for ldap_389ds_replication_status --- exporter/ldap.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/ldap.go b/exporter/ldap.go index 2db34e6..6527710 100644 --- a/exporter/ldap.go +++ b/exporter/ldap.go @@ -85,7 +85,7 @@ var ( Name: "replication_status", Help: "Replication status by server", }, - []string{"server"}, + []string{"destination"}, ) scrapeCounter = prometheus.NewCounterVec( prometheus.CounterOpts{