All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.titisan.ldap.LdapCollector Maven / Gradle / Ivy

The newest version!
package com.titisan.ldap;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.management.MalformedObjectNameException;

import org.yaml.snakeyaml.Yaml;
import static java.lang.String.format;

import io.prometheus.client.Collector;
import io.prometheus.client.Counter;

public class LdapCollector extends Collector implements Collector.Describable {
    static final Counter configReloadSuccess = Counter.build()
      .name("ldapexporter_config_reload_success_total")
      .help("Number of times configuration have successfully been reloaded.").register();

    static final Counter configReloadFailure = Counter.build()
      .name("ldapexporter_config_reload_failure_total")
      .help("Number of times configuration have failed to be reloaded.").register();

    private static final Logger LOGGER = Logger.getLogger(LdapCollector.class.getName());

    private static class Rule {
      Pattern pattern;
      String name;
      String value;
      Double valueFactor = 1.0;
      String help;
      Type type = Type.UNTYPED;
      Boolean continue_next = false;
      ArrayList labelNames;
      ArrayList labelValues;
    }

    private static class Config {
      Integer startDelaySeconds = 0;
      String ldapUrl = "ldap://127.0.0.1:389"; //default ldap URL if not provided
      String username = "";
      String password = "";
      String baseDN = "cn=Monitor"; //default baseDN
      boolean lowercaseOutputName;
      boolean lowercaseOutputLabelNames;
      List whitelistEntryNames = new ArrayList();
      List blacklistEntryNames = new ArrayList();
      List extraAttrsToReturn = new ArrayList();
      ArrayList rules = new ArrayList();
      long lastUpdate = 0L;
    }

    private Config config;
    private File configFile;
    private long createTimeNanoSecs = System.nanoTime();

    public LdapCollector(File in) throws IOException, MalformedObjectNameException {
        configFile = in;
        FileReader F_reader = null;
        try {
          F_reader = new FileReader(in); 
          config = loadConfig((Map) new Yaml().load(F_reader));
          config.lastUpdate = configFile.lastModified();
        } catch (IOException  e) {
          LOGGER.severe("Configuration load failed: " + e.toString());
        } catch (MalformedObjectNameException e) {
          LOGGER.severe("Configuration load failed: " + e.toString());
        }
        finally {
          if (F_reader != null)  {
            F_reader.close();
          }
        }
    }

    public LdapCollector(String yamlConfig) throws MalformedObjectNameException {
        config = loadConfig((Map)new Yaml().load(yamlConfig));
    }

    private void reloadConfig() {
      try {
        FileReader fr = new FileReader(configFile);

        try {
          Map newYamlConfig = (Map)new Yaml().load(fr);
          config = loadConfig(newYamlConfig);
          config.lastUpdate = configFile.lastModified();
          configReloadSuccess.inc();
        } catch (Exception e) {
          LOGGER.severe("Configuration reload failed: " + e.toString());
          configReloadFailure.inc();
        } finally {
          fr.close();
        }

      } catch (IOException e) {
        LOGGER.severe("Configuration reload failed: " + e.toString());
        configReloadFailure.inc();
      }
    }

    private Config loadConfig(Map yamlConfig) throws MalformedObjectNameException {
        Config cfg = new Config();

        if (yamlConfig == null) {  // Yaml config empty, set config to empty map.
          yamlConfig = new HashMap();
        }

        if (yamlConfig.containsKey("startDelaySeconds")) {
          try {
            cfg.startDelaySeconds = (Integer) yamlConfig.get("startDelaySeconds");
          } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid number provided for startDelaySeconds", e);
          }
        }
        //ldapUrl is optional, if not present default is "ldap://127.0.0.1:389"
        if (yamlConfig.containsKey("ldapUrl")) {
          cfg.ldapUrl = (String)yamlConfig.get("ldapUrl");
        }

        if (yamlConfig.containsKey("username")) {
          cfg.username = (String)yamlConfig.get("username");
        }
        
        if (yamlConfig.containsKey("password")) {
          cfg.password = (String)yamlConfig.get("password");
        }
        
        //baseDN is optional, if not present default is "cn=Monitor"
        if (yamlConfig.containsKey("baseDN")) {
          cfg.baseDN = (String)yamlConfig.get("baseDN");
        }
      
        if (yamlConfig.containsKey("lowercaseOutputName")) {
          cfg.lowercaseOutputName = (Boolean)yamlConfig.get("lowercaseOutputName");
        }

        if (yamlConfig.containsKey("lowercaseOutputLabelNames")) {
          cfg.lowercaseOutputLabelNames = (Boolean)yamlConfig.get("lowercaseOutputLabelNames");
        }

        if (yamlConfig.containsKey("whitelistEntryNames")) {
          List names = (List) yamlConfig.get("whitelistEntryNames");
          for(String name : names) {
            cfg.whitelistEntryNames.add(name);
          }
        } 

        if (yamlConfig.containsKey("blacklistEntryNames")) {
          List names = (List) yamlConfig.get("blacklistEntryNames");
          for (String name : names) {
            cfg.blacklistEntryNames.add(name);
          }
        }

        if (yamlConfig.containsKey("extraAttributesToReturn")) {
         List names = (List) yamlConfig.get("extraAttributesToReturn");
         for (String name : names) {
           cfg.extraAttrsToReturn.add(name);
         }
       }

        if (yamlConfig.containsKey("rules")) {
          List> configRules = (List>) yamlConfig.get("rules");
          for (Map ruleObject : configRules) {
            Map yamlRule = ruleObject;
            Rule rule = new Rule();

            if (yamlRule.containsKey("pattern")) {
              try {
                rule.pattern = Pattern.compile((String)yamlRule.get("pattern"));
              } catch (PatternSyntaxException e) {
                // ignore this rule if wrong pattern
                LOGGER.warning("Ignoring wrong patern: " + (String)yamlRule.get("pattern")  + " Error: " + e.toString());
                continue;
              }
              
            }
            if (yamlRule.containsKey("name")) {
              rule.name = (String)yamlRule.get("name");
            }
            if (yamlRule.containsKey("value")) {
              rule.value = String.valueOf(yamlRule.get("value"));
            }
            if (yamlRule.containsKey("valueFactor")) {
              String valueFactor = String.valueOf(yamlRule.get("valueFactor"));
              try {
                rule.valueFactor = Double.valueOf(valueFactor);
              } catch (NumberFormatException e) {
                // use default value
              }
            }
            if (yamlRule.containsKey("type")) {
              rule.type = Type.valueOf((String)yamlRule.get("type"));
            }
            if (yamlRule.containsKey("help")) {
              rule.help = (String)yamlRule.get("help");
            }
            if (yamlRule.containsKey("labels")) {
              TreeMap labels = new TreeMap((Map)yamlRule.get("labels"));
              rule.labelNames = new ArrayList();
              rule.labelValues = new ArrayList();
              for (Map.Entry entry : (Set>)labels.entrySet()) {
                rule.labelNames.add(entry.getKey());
                rule.labelValues.add((String)entry.getValue());
              }
            }
            
            if (yamlRule.containsKey("continue")) {
              rule.continue_next = (Boolean)yamlRule.get("continue");
            }

            // Validation.
            if ((rule.labelNames != null || rule.help != null) && rule.name == null) {
              throw new IllegalArgumentException("Must provide name, if help or labels are given: " + yamlRule);
            }
            if (rule.name != null && rule.pattern == null) {
              throw new IllegalArgumentException("Must provide pattern, if name is given: " + yamlRule);
            }
            // Add the rule to the configured rule (only after the validations are done)
            cfg.rules.add(rule);
          }
        } else {
          // Default to a single default rule.
          cfg.rules.add(new Rule());
        }

        return cfg;

    }

    class Receiver implements LdapScraper.LdapReceiver {
      Map metricFamilySamplesMap =
        new HashMap();

      private final Pattern unsafeChars = Pattern.compile("[^a-zA-Z0-9:_]");
      private final Pattern multipleUnderscores = Pattern.compile("__+");
      private final Pattern cnequals = Pattern.compile(",*cn=");

      private String safeName(String s) {
        // Change invalid chars to underscore, and merge underscores.
        return multipleUnderscores.matcher(unsafeChars.matcher(cnequals.matcher(s).replaceAll("_")).replaceAll("_")).replaceAll("_");
      }

      void addSample(MetricFamilySamples.Sample sample, Type type, String help) {
        MetricFamilySamples mfs = metricFamilySamplesMap.get(sample.name);
        if (mfs == null) {
          // LdapScraper.LdapReceiver is only called from one thread,
          // so there's no race here.
          mfs = new MetricFamilySamples(sample.name, type, help, new ArrayList());
          metricFamilySamplesMap.put(sample.name, mfs);
        }
        mfs.samples.add(sample);
      }

      private void defaultExport(
          String entryName,
          String attrName,
          String help,
          Number value,
          Type type) {
          
        String fullname = safeName(entryName);

        if (config.lowercaseOutputName) {
          fullname = fullname.toLowerCase();
        }
         // Add to samples.
         LOGGER.fine("add metric sample, Name: " + fullname + 
         " Value: " + value.doubleValue() + 
         " help: " + help);
         addSample(new MetricFamilySamples.Sample(fullname, new ArrayList(), new ArrayList(), value.doubleValue()), type, help);
      }

      public void recordLdapEntry(
        String entryName,
        Number counterValue,
        String attrName,
        String attrDescription) {

        String help = "Metric from " + attrDescription;

        for (Rule rule : config.rules) {
          Matcher matcher = null;
          if (rule.pattern != null) {
            matcher = rule.pattern.matcher(entryName);
            if (!matcher.matches()) {
              continue;
            } 
          }

          Number value = Double.valueOf(0.0);
          if (rule.value != null && !rule.value.isEmpty()) {
            String val = matcher.replaceAll(rule.value);

            try {
                counterValue = Double.valueOf(val);
            } catch (NumberFormatException e) {
              LOGGER.fine("Unable to parse configured value '" + val + "' to number for entry: " + entryName + "_" + attrName + ": " + counterValue);
              return;
            }
          }
          value = ((Number)counterValue).doubleValue() * rule.valueFactor;

          // If there's no name provided, use default export format.
          if (rule.name == null) {
              //LOGGER.fine("No rule name provided, using defaultExport: " + entryName);
              defaultExport(entryName, attrName, help, value, Type.UNTYPED);
              if (!rule.continue_next) {
                return;
              } else {
                continue;
              }

          }

          // Matcher is set below here due to validation in the constructor.
          String name = safeName(matcher.replaceAll(rule.name));
          if (name.isEmpty()) {
            return;
          }
          if (config.lowercaseOutputName) {
            name = name.toLowerCase();
          }

          // Set the help.
          if (rule.help != null) {
            help = matcher.replaceAll(rule.help);
          }

          // Set the labels.
          ArrayList labelNames = new ArrayList();
          ArrayList labelValues = new ArrayList();
          if (rule.labelNames != null) {
            for (int i = 0; i < rule.labelNames.size(); i++) {
              final String unsafeLabelName = rule.labelNames.get(i);
              final String labelValReplacement = rule.labelValues.get(i);
              try {
                String labelName = safeName(matcher.replaceAll(unsafeLabelName));
                String labelValue = matcher.replaceAll(labelValReplacement);
                if (config.lowercaseOutputLabelNames) {
                  labelName = labelName.toLowerCase();
                }
                if (!labelName.isEmpty() && !labelValue.isEmpty()) {
                  labelNames.add(labelName);
                  labelValues.add(labelValue);
                }
              } catch (Exception e) {
                throw new RuntimeException(
                  format("Matcher '%s' unable to use: '%s' value: '%s'", matcher, unsafeLabelName, labelValReplacement), e);
              }
            }
          }

          // Add to samples.
          LOGGER.fine("add metric sample, Name: " + name + 
                      " Value: " + value.doubleValue() + 
                      " Labels: " + labelNames.toString() +
                      " Label values: " + labelValues.toString() +
                      " help: " + help);
          addSample(new MetricFamilySamples.Sample(name, labelNames, labelValues, value.doubleValue()), rule.type, help);
          if (!rule.continue_next) {
            return;
          } else {
            continue;
          }
        }
      }

    }

    public List collect() {
      if (configFile != null) {
        long mtime = configFile.lastModified();
        if (mtime > config.lastUpdate) {
          LOGGER.fine("Configuration file changed, reloading...");
          reloadConfig();
        }
      }

      Receiver receiver = new Receiver();
      LdapScraper scraper = new LdapScraper(config.ldapUrl, config.username, config.password, config.baseDN, config.whitelistEntryNames, config.blacklistEntryNames, config.extraAttrsToReturn, receiver);
      long start = System.nanoTime();
      double error = 0;
      if ((config.startDelaySeconds > 0) &&
        ((start - createTimeNanoSecs) / 1000000000L < config.startDelaySeconds)) {
        throw new IllegalStateException("LdapCollector waiting for startDelaySeconds");
      }
      try {
        scraper.doScrape();
      } catch (Exception e) {
        error = 1;
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        LOGGER.severe("LDAP scrape failed: " + sw.toString());
      }
      List mfsList = new ArrayList();
      mfsList.addAll(receiver.metricFamilySamplesMap.values());
      List samples = new ArrayList();
      samples.add(new MetricFamilySamples.Sample(
          "ldap_scrape_duration_seconds", new ArrayList(), new ArrayList(), (System.nanoTime() - start) / 1.0E9));
      mfsList.add(new MetricFamilySamples("ldap_scrape_duration_seconds", Type.GAUGE, "Time this LDAP scrape took, in seconds.", samples));

      samples = new ArrayList();
      samples.add(new MetricFamilySamples.Sample(
          "ldap_scrape_error", new ArrayList(), new ArrayList(), error));
      mfsList.add(new MetricFamilySamples("ldap_scrape_error", Type.GAUGE, "Non-zero if this scrape failed.", samples));
      return mfsList;
    }

    public List describe() {
      List sampleFamilies = new ArrayList();
      sampleFamilies.add(new MetricFamilySamples("ldap_scrape_duration_seconds", Type.GAUGE, "Time this LDAP scrape took, in seconds.", new ArrayList()));
      sampleFamilies.add(new MetricFamilySamples("ldap_scrape_error", Type.GAUGE, "Non-zero if this scrape failed.", new ArrayList()));
      return sampleFamilies;
    }

    /**
     * Convenience function to run standalone.
     */
    public static void main(String[] args) throws Exception {
      String ldapUrl = "ldap://127.0.0.1:389";
      if (args.length > 0) {
        ldapUrl = args[0];
      }
      LdapCollector lc = new LdapCollector("\n---\nldapUrl: " + ldapUrl);
      
      for(MetricFamilySamples mfs : lc.collect()) {
        System.out.println(mfs);
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy