diff --git a/cmd/openldap_exporter/main.go b/cmd/openldap_exporter/main.go index 4cebed4..45f77ed 100644 --- a/cmd/openldap_exporter/main.go +++ b/cmd/openldap_exporter/main.go @@ -16,16 +16,17 @@ import ( ) const ( - promAddr = "promAddr" - ldapNet = "ldapNet" - ldapAddr = "ldapAddr" - ldapUser = "ldapUser" - ldapPass = "ldapPass" - interval = "interval" - metrics = "metrPath" - jsonLog = "jsonLog" - webCfgFile = "webCfgFile" - config = "config" + promAddr = "promAddr" + ldapNet = "ldapNet" + ldapAddr = "ldapAddr" + ldapUser = "ldapUser" + ldapPass = "ldapPass" + interval = "interval" + metrics = "metrPath" + jsonLog = "jsonLog" + webCfgFile = "webCfgFile" + config = "config" + replicationObject = "replicationObject" ) func main() { @@ -51,7 +52,7 @@ func main() { altsrc.NewStringFlag(&cli.StringFlag{ Name: ldapAddr, Value: "localhost:389", - Usage: "Address of OpenLDAP server", + Usage: "Address and port of OpenLDAP server", EnvVars: []string{"LDAP_ADDR"}, }), altsrc.NewStringFlag(&cli.StringFlag{ @@ -64,6 +65,7 @@ func main() { Usage: "OpenLDAP bind password (optional)", EnvVars: []string{"LDAP_PASS"}, }), + altsrc.NewDurationFlag(&cli.DurationFlag{ Name: interval, Value: 30 * time.Second, @@ -81,6 +83,10 @@ func main() { Usage: "Output logs in JSON format", EnvVars: []string{"JSON_LOG"}, }), + altsrc.NewStringSliceFlag(&cli.StringSliceFlag{ + Name: replicationObject, + Usage: "Object to watch replication uppon", + }), &cli.StringFlag{ Name: config, Usage: "Optional configuration from a `YAML_FILE`", @@ -131,6 +137,7 @@ func runMain(c *cli.Context) error { User: c.String(ldapUser), Pass: c.String(ldapPass), Tick: c.Duration(interval), + Sync: c.StringSlice(replicationObject), } ctx, cancel := context.WithCancel(context.Background()) diff --git a/scraper.go b/scraper.go index 7a3753e..aac7ed6 100644 --- a/scraper.go +++ b/scraper.go @@ -24,6 +24,9 @@ const ( monitorOperation = "monitorOperation" monitorOpCompleted = "monitorOpCompleted" + + monitorReplicationFilter = "contextcsn" + monitorReplication = "monitorReplication" ) type query struct { @@ -31,6 +34,7 @@ type query struct { searchFilter string searchAttr string metric *prometheus.GaugeVec + setData func([]*ldap.Entry, *query) error } var ( @@ -66,24 +70,50 @@ var ( }, []string{"result"}, ) + monitorReplicationGauge = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: "openldap", + Name: "monitor_replication", + Help: help(baseDN, monitorReplication), + }, + []string{"id", "type"}, + ) queries = []*query{ { baseDN: baseDN, searchFilter: objectClass(monitoredObject), searchAttr: monitoredInfo, metric: monitoredObjectGauge, - }, - { + setData: func(entries []*ldap.Entry, q *query) error { + return setValue(entries, q) + + }, + }, { baseDN: baseDN, searchFilter: objectClass(monitorCounterObject), searchAttr: monitorCounter, metric: monitorCounterObjectGauge, + setData: func(entries []*ldap.Entry, q *query) error { + return setValue(entries, q) + }, + }, + { + baseDN: opsBaseDN, + searchFilter: objectClass(monitorOperation), + searchAttr: monitorOpCompleted, + metric: monitorOperationGauge, + setData: func(entries []*ldap.Entry, q *query) error { + return setValue(entries, q) + }, }, { baseDN: opsBaseDN, searchFilter: objectClass(monitorOperation), searchAttr: monitorOpCompleted, metric: monitorOperationGauge, + setData: func(entries []*ldap.Entry, q *query) error { + return setValue(entries, q) + }, }, } ) @@ -93,6 +123,7 @@ func init() { monitoredObjectGauge, monitorCounterObjectGauge, monitorOperationGauge, + monitorReplicationGauge, scrapeCounter, ) } @@ -105,17 +136,81 @@ func objectClass(name string) string { return fmt.Sprintf("(objectClass=%v)", name) } +func setValue(entries []*ldap.Entry, q *query) error { + for _, entry := range entries { + val := entry.GetAttributeValue(q.searchAttr) + if val == "" { + // not every entry will have this attribute + continue + } + num, err := strconv.ParseFloat(val, 64) + if err != nil { + // some of these attributes are not numbers + continue + } + q.metric.WithLabelValues(entry.DN).Set(num) + } + return nil + +} + type Scraper struct { - Net string - Addr string - User string - Pass string - Tick time.Duration - log log.FieldLogger + Net string + Addr string + User string + Pass string + Tick time.Duration + LdapSync []string + log log.FieldLogger + Sync []string +} + +func (s *Scraper) addReplicationQueries() error { + if len(s.Sync) != 0 { + for _, q := range s.Sync { + queries = append(queries, + &query{ + baseDN: q, + searchFilter: objectClass("*"), + searchAttr: "contextCSN", + metric: monitorReplicationGauge, + setData: func(entries []*ldap.Entry, q *query) error { + for _, entry := range entries { + val := entry.GetAttributeValue(q.searchAttr) + if val == "" { + // not every entry will have this attribute + continue + } + valueBuffer := strings.Split(val, "#") + gt, err := time.Parse("20060102150405.999999Z", valueBuffer[0]) + if err != nil { + return err + } + count, err := strconv.ParseFloat(valueBuffer[1], 64) + if err != nil { + return err + } + sid := valueBuffer[2] + mod, err := strconv.ParseFloat(valueBuffer[3], 64) + if err != nil { + return err + } + q.metric.WithLabelValues(sid, "gt").Set(float64(gt.Unix())) + q.metric.WithLabelValues(sid, "count").Set(count) + q.metric.WithLabelValues(sid, "mod").Set(mod) + } + return nil + }, + }, + ) + } + } + return nil } func (s *Scraper) Start(ctx context.Context) error { s.log = log.WithField("component", "scraper") + s.addReplicationQueries() address := fmt.Sprintf("%s://%s", s.Net, s.Addr) s.log.WithField("addr", address).Info("starting monitor loop") ticker := time.NewTicker(s.Tick) @@ -167,24 +262,11 @@ func (s *Scraper) scrape() bool { func scrapeQuery(conn *ldap.Conn, q *query) error { req := ldap.NewSearchRequest( q.baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - q.searchFilter, []string{"dn", q.searchAttr}, nil, + q.searchFilter, []string{q.searchAttr}, nil, ) sr, err := conn.Search(req) if err != nil { return err } - for _, entry := range sr.Entries { - val := entry.GetAttributeValue(q.searchAttr) - if val == "" { - // not every entry will have this attribute - continue - } - num, err := strconv.ParseFloat(val, 64) - if err != nil { - // some of these attributes are not numbers - continue - } - q.metric.WithLabelValues(entry.DN).Set(num) - } - return nil + return q.setData(sr.Entries, q) }