...

Source file src/github.com/letsencrypt/boulder/observer/obs_conf.go

Documentation: github.com/letsencrypt/boulder/observer

     1  package observer
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  
     9  	"github.com/prometheus/client_golang/prometheus"
    10  
    11  	"github.com/letsencrypt/boulder/cmd"
    12  	"github.com/letsencrypt/boulder/observer/probers"
    13  )
    14  
    15  var (
    16  	countMonitors = prometheus.NewCounterVec(
    17  		prometheus.CounterOpts{
    18  			Name: "obs_monitors",
    19  			Help: "details of each configured monitor",
    20  		},
    21  		[]string{"kind", "valid"},
    22  	)
    23  	histObservations *prometheus.HistogramVec
    24  )
    25  
    26  // ObsConf is exported to receive YAML configuration.
    27  type ObsConf struct {
    28  	DebugAddr     string           `yaml:"debugaddr" validate:"required,hostname_port"`
    29  	Buckets       []float64        `yaml:"buckets" validate:"min=1,dive"`
    30  	Syslog        cmd.SyslogConfig `yaml:"syslog"`
    31  	OpenTelemetry cmd.OpenTelemetryConfig
    32  	MonConfs      []*MonConf `yaml:"monitors" validate:"min=1,dive"`
    33  }
    34  
    35  // validateSyslog ensures the the `Syslog` field received by `ObsConf`
    36  // contains valid log levels.
    37  func (c *ObsConf) validateSyslog() error {
    38  	syslog, stdout := c.Syslog.SyslogLevel, c.Syslog.StdoutLevel
    39  	if stdout < 0 || stdout > 7 || syslog < 0 || syslog > 7 {
    40  		return fmt.Errorf(
    41  			"invalid 'syslog', '%+v', valid log levels are 0-7", c.Syslog)
    42  	}
    43  	return nil
    44  }
    45  
    46  // validateDebugAddr ensures the `debugAddr` received by `ObsConf` is
    47  // properly formatted and a valid port.
    48  func (c *ObsConf) validateDebugAddr() error {
    49  	_, p, err := net.SplitHostPort(c.DebugAddr)
    50  	if err != nil {
    51  		return fmt.Errorf(
    52  			"invalid 'debugaddr', %q, not expected format", c.DebugAddr)
    53  	}
    54  	port, _ := strconv.Atoi(p)
    55  	if port <= 0 || port > 65535 {
    56  		return fmt.Errorf(
    57  			"invalid 'debugaddr','%d' is not a valid port", port)
    58  	}
    59  	return nil
    60  }
    61  
    62  func (c *ObsConf) makeMonitors(metrics prometheus.Registerer) ([]*monitor, []error, error) {
    63  	var errs []error
    64  	var monitors []*monitor
    65  	proberSpecificMetrics := make(map[string]map[string]prometheus.Collector)
    66  	for e, m := range c.MonConfs {
    67  		entry := strconv.Itoa(e + 1)
    68  		proberConf, err := probers.GetConfigurer(m.Kind)
    69  		if err != nil {
    70  			// append error to errs
    71  			errs = append(errs, fmt.Errorf("'monitors' entry #%s couldn't be validated: %w", entry, err))
    72  			// increment metrics
    73  			countMonitors.WithLabelValues(m.Kind, "false").Inc()
    74  			// bail out before constructing the monitor. with no configurer, it will fail
    75  			continue
    76  		}
    77  		kind := proberConf.Kind()
    78  
    79  		// set up custom metrics internal to each prober kind
    80  		_, exist := proberSpecificMetrics[kind]
    81  		if !exist {
    82  			// we haven't seen this prober kind before, so we need to request
    83  			// any custom metrics it may have and register them with the
    84  			// prometheus registry
    85  			proberSpecificMetrics[kind] = make(map[string]prometheus.Collector)
    86  			for name, collector := range proberConf.Instrument() {
    87  				// register the collector with the prometheus registry
    88  				metrics.MustRegister(collector)
    89  				// store the registered collector so we can pass it to every
    90  				// monitor that will construct this kind of prober
    91  				proberSpecificMetrics[kind][name] = collector
    92  			}
    93  		}
    94  
    95  		monitor, err := m.makeMonitor(proberSpecificMetrics[kind])
    96  		if err != nil {
    97  			// append validation error to errs
    98  			errs = append(errs, fmt.Errorf("'monitors' entry #%s couldn't be validated: %w", entry, err))
    99  
   100  			// increment metrics
   101  			countMonitors.WithLabelValues(kind, "false").Inc()
   102  		} else {
   103  			// append monitor to monitors
   104  			monitors = append(monitors, monitor)
   105  
   106  			// increment metrics
   107  			countMonitors.WithLabelValues(kind, "true").Inc()
   108  		}
   109  	}
   110  	if len(c.MonConfs) == len(errs) {
   111  		return nil, errs, errors.New("no valid monitors, cannot continue")
   112  	}
   113  	return monitors, errs, nil
   114  }
   115  
   116  // MakeObserver constructs an `Observer` object from the contents of the
   117  // bound `ObsConf`. If the `ObsConf` cannot be validated, an error
   118  // appropriate for end-user consumption is returned instead.
   119  func (c *ObsConf) MakeObserver() (*Observer, error) {
   120  	err := c.validateSyslog()
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	err = c.validateDebugAddr()
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	if len(c.MonConfs) == 0 {
   131  		return nil, errors.New("no monitors provided")
   132  	}
   133  
   134  	if len(c.Buckets) == 0 {
   135  		return nil, errors.New("no histogram buckets provided")
   136  	}
   137  
   138  	// Start monitoring and logging.
   139  	metrics, logger, shutdown := cmd.StatsAndLogging(c.Syslog, c.OpenTelemetry, c.DebugAddr)
   140  	histObservations = prometheus.NewHistogramVec(
   141  		prometheus.HistogramOpts{
   142  			Name:    "obs_observations",
   143  			Help:    "details of each probe attempt",
   144  			Buckets: c.Buckets,
   145  		}, []string{"name", "kind", "success"})
   146  	metrics.MustRegister(countMonitors)
   147  	metrics.MustRegister(histObservations)
   148  	defer cmd.AuditPanic()
   149  	logger.Info(cmd.VersionString())
   150  	logger.Infof("Initializing boulder-observer daemon")
   151  	logger.Debugf("Using config: %+v", c)
   152  
   153  	monitors, errs, err := c.makeMonitors(metrics)
   154  	if len(errs) != 0 {
   155  		logger.Errf("%d of %d monitors failed validation", len(errs), len(c.MonConfs))
   156  		for _, err := range errs {
   157  			logger.Errf("%s", err)
   158  		}
   159  	} else {
   160  		logger.Info("all monitors passed validation")
   161  	}
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	return &Observer{logger, monitors, shutdown}, nil
   166  }
   167  

View as plain text