282 lines
7.6 KiB
Go
282 lines
7.6 KiB
Go
|
// Licensed to Elasticsearch B.V. under one or more contributor
|
||
|
// license agreements. See the NOTICE file distributed with
|
||
|
// this work for additional information regarding copyright
|
||
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
||
|
// the Apache License, Version 2.0 (the "License"); you may
|
||
|
// not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing,
|
||
|
// software distributed under the License is distributed on an
|
||
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||
|
// KIND, either express or implied. See the License for the
|
||
|
// specific language governing permissions and limitations
|
||
|
// under the License.
|
||
|
|
||
|
package mb
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/gofrs/uuid"
|
||
|
"github.com/joeshaw/multierror"
|
||
|
"github.com/pkg/errors"
|
||
|
|
||
|
"github.com/elastic/beats/libbeat/common"
|
||
|
"github.com/elastic/beats/libbeat/monitoring"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
// ErrEmptyConfig indicates that modules configuration list is nil or empty.
|
||
|
ErrEmptyConfig = errors.New("one or more modules must be configured")
|
||
|
|
||
|
// ErrAllModulesDisabled indicates that all modules are disabled. At least
|
||
|
// one module must be enabled.
|
||
|
ErrAllModulesDisabled = errors.New("all modules are disabled")
|
||
|
|
||
|
// ErrModuleDisabled indicates a disabled module has been tried to instantiate.
|
||
|
ErrModuleDisabled = errors.New("disabled module")
|
||
|
)
|
||
|
|
||
|
// NewModule builds a new Module and its associated MetricSets based on the
|
||
|
// provided configuration data. config contains config data (the data
|
||
|
// will be unpacked into ModuleConfig structs). r is the Register where the
|
||
|
// ModuleFactory's and MetricSetFactory's will be obtained from. This method
|
||
|
// returns a Module and its configured MetricSets or an error.
|
||
|
func NewModule(config *common.Config, r *Register) (Module, []MetricSet, error) {
|
||
|
if !config.Enabled() {
|
||
|
return nil, nil, ErrModuleDisabled
|
||
|
}
|
||
|
|
||
|
bm, err := newBaseModuleFromConfig(config)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
module, err := createModule(r, bm)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
metricsets, err := initMetricSets(r, module)
|
||
|
if err != nil {
|
||
|
return nil, nil, err
|
||
|
}
|
||
|
|
||
|
return module, metricsets, nil
|
||
|
}
|
||
|
|
||
|
// newBaseModuleFromConfig creates a new BaseModule from config. The returned
|
||
|
// BaseModule's name will always be lower case.
|
||
|
func newBaseModuleFromConfig(rawConfig *common.Config) (BaseModule, error) {
|
||
|
baseModule := BaseModule{
|
||
|
config: DefaultModuleConfig(),
|
||
|
rawConfig: rawConfig,
|
||
|
}
|
||
|
err := rawConfig.Unpack(&baseModule.config)
|
||
|
if err != nil {
|
||
|
return baseModule, err
|
||
|
}
|
||
|
|
||
|
// If timeout is not set, timeout is set to the same value as period
|
||
|
if baseModule.config.Timeout == 0 {
|
||
|
baseModule.config.Timeout = baseModule.config.Period
|
||
|
}
|
||
|
|
||
|
baseModule.name = strings.ToLower(baseModule.config.Module)
|
||
|
|
||
|
err = mustNotContainDuplicates(baseModule.config.Hosts)
|
||
|
if err != nil {
|
||
|
return baseModule, errors.Wrapf(err, "invalid hosts for module '%s'", baseModule.name)
|
||
|
}
|
||
|
|
||
|
return baseModule, nil
|
||
|
}
|
||
|
|
||
|
func createModule(r *Register, bm BaseModule) (Module, error) {
|
||
|
f := r.moduleFactory(bm.Name())
|
||
|
if f == nil {
|
||
|
f = DefaultModuleFactory
|
||
|
}
|
||
|
|
||
|
return f(bm)
|
||
|
}
|
||
|
|
||
|
func initMetricSets(r *Register, m Module) ([]MetricSet, error) {
|
||
|
var (
|
||
|
errs multierror.Errors
|
||
|
metricsets []MetricSet
|
||
|
)
|
||
|
|
||
|
bms, err := newBaseMetricSets(r, m)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
for _, bm := range bms {
|
||
|
registration, err := r.metricSetRegistration(bm.Module().Name(), bm.Name())
|
||
|
if err != nil {
|
||
|
errs = append(errs, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
bm.registration = registration
|
||
|
bm.hostData = HostData{URI: bm.host}
|
||
|
if registration.HostParser != nil {
|
||
|
bm.hostData, err = registration.HostParser(bm.Module(), bm.host)
|
||
|
if err != nil {
|
||
|
errs = append(errs, errors.Wrapf(err, "host parsing failed for %v-%v",
|
||
|
bm.Module().Name(), bm.Name()))
|
||
|
continue
|
||
|
}
|
||
|
bm.host = bm.hostData.Host
|
||
|
}
|
||
|
|
||
|
metricSet, err := registration.Factory(bm)
|
||
|
if err == nil {
|
||
|
err = mustHaveModule(metricSet, bm)
|
||
|
if err == nil {
|
||
|
err = mustImplementFetcher(metricSet)
|
||
|
}
|
||
|
}
|
||
|
if err != nil {
|
||
|
errs = append(errs, err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
metricsets = append(metricsets, metricSet)
|
||
|
}
|
||
|
|
||
|
return metricsets, errs.Err()
|
||
|
}
|
||
|
|
||
|
// newBaseMetricSets creates a new BaseMetricSet for all MetricSets defined
|
||
|
// in the module's config. An error is returned if no MetricSets are specified
|
||
|
// in the module's config and no default MetricSet is defined.
|
||
|
func newBaseMetricSets(r *Register, m Module) ([]BaseMetricSet, error) {
|
||
|
hosts := []string{""}
|
||
|
if l := m.Config().Hosts; len(l) > 0 {
|
||
|
hosts = l
|
||
|
}
|
||
|
|
||
|
metricSetNames := m.Config().MetricSets
|
||
|
if len(metricSetNames) == 0 {
|
||
|
var err error
|
||
|
metricSetNames, err = r.DefaultMetricSets(m.Name())
|
||
|
if err != nil {
|
||
|
return nil, errors.Errorf("no metricsets configured for module '%s'", m.Name())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var metricsets []BaseMetricSet
|
||
|
for _, name := range metricSetNames {
|
||
|
name = strings.ToLower(name)
|
||
|
for _, host := range hosts {
|
||
|
id, err := uuid.NewV4()
|
||
|
if err != nil {
|
||
|
return nil, errors.Wrap(err, "failed to generate ID for metricset")
|
||
|
}
|
||
|
msID := id.String()
|
||
|
metrics := monitoring.NewRegistry()
|
||
|
monitoring.NewString(metrics, "module").Set(m.Name())
|
||
|
monitoring.NewString(metrics, "metricset").Set(name)
|
||
|
if host != "" {
|
||
|
monitoring.NewString(metrics, "host").Set(host)
|
||
|
}
|
||
|
monitoring.NewString(metrics, "id").Set(msID)
|
||
|
|
||
|
metricsets = append(metricsets, BaseMetricSet{
|
||
|
id: msID,
|
||
|
name: name,
|
||
|
module: m,
|
||
|
host: host,
|
||
|
metrics: metrics,
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
return metricsets, nil
|
||
|
}
|
||
|
|
||
|
// mustHaveModule returns an error if the given MetricSet's Module() method
|
||
|
// returns nil. This validation ensures that all MetricSet implementations
|
||
|
// honor the interface contract.
|
||
|
func mustHaveModule(ms MetricSet, base BaseMetricSet) error {
|
||
|
if ms.Module() == nil {
|
||
|
return fmt.Errorf("%s module cannot be nil in %T", base.module.Name(), ms)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// mustImplementFetcher returns an error if the given MetricSet does not
|
||
|
// implement one of the Fetcher interface or if it implements more than one
|
||
|
// of them.
|
||
|
func mustImplementFetcher(ms MetricSet) error {
|
||
|
var ifcs []string
|
||
|
if _, ok := ms.(EventFetcher); ok {
|
||
|
ifcs = append(ifcs, "EventFetcher")
|
||
|
}
|
||
|
|
||
|
if _, ok := ms.(EventsFetcher); ok {
|
||
|
ifcs = append(ifcs, "EventsFetcher")
|
||
|
}
|
||
|
|
||
|
if _, ok := ms.(ReportingMetricSet); ok {
|
||
|
ifcs = append(ifcs, "ReportingMetricSet")
|
||
|
}
|
||
|
|
||
|
if _, ok := ms.(PushMetricSet); ok {
|
||
|
ifcs = append(ifcs, "PushMetricSet")
|
||
|
}
|
||
|
|
||
|
if _, ok := ms.(ReportingMetricSetV2); ok {
|
||
|
ifcs = append(ifcs, "ReportingMetricSetV2")
|
||
|
}
|
||
|
|
||
|
if _, ok := ms.(PushMetricSetV2); ok {
|
||
|
ifcs = append(ifcs, "PushMetricSetV2")
|
||
|
}
|
||
|
|
||
|
switch len(ifcs) {
|
||
|
case 0:
|
||
|
return fmt.Errorf("MetricSet '%s/%s' does not implement an event "+
|
||
|
"producing interface (EventFetcher, EventsFetcher, "+
|
||
|
"ReportingMetricSet, ReportingMetricSetV2, PushMetricSet, or "+
|
||
|
"PushMetricSetV2)",
|
||
|
ms.Module().Name(), ms.Name())
|
||
|
case 1:
|
||
|
return nil
|
||
|
default:
|
||
|
return fmt.Errorf("MetricSet '%s/%s' can only implement a single "+
|
||
|
"event producing interface, but implements %v", ms.Module().Name(),
|
||
|
ms.Name(), ifcs)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// mustNotContainDuplicates returns an error if the given slice contains
|
||
|
// duplicate values.
|
||
|
func mustNotContainDuplicates(s []string) error {
|
||
|
duplicates := map[string]struct{}{}
|
||
|
set := make(map[string]struct{}, len(s))
|
||
|
for _, v := range s {
|
||
|
_, encountered := set[v]
|
||
|
if encountered {
|
||
|
duplicates[v] = struct{}{}
|
||
|
continue
|
||
|
}
|
||
|
set[v] = struct{}{}
|
||
|
}
|
||
|
|
||
|
if len(duplicates) > 0 {
|
||
|
var keys []string
|
||
|
for dup := range duplicates {
|
||
|
keys = append(keys, dup)
|
||
|
}
|
||
|
return fmt.Errorf("duplicates detected [%s]", strings.Join(keys, ", "))
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|