-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add secondary source of modules to Metricbeat to load light modules (#…
…12465) Implements #12270. Modify metricbeat modules registry to include an optional secondary modules source. A modules registry uses the secondary source as a fallback when it cannot provide the configuration for an specific metricset. Semantics of current methods are maintained. An implementation for a secondary source is provided. This implementation loads modules from disk that depend on traditional registered modules. Once loaded, a "registration" object can be obtained, what includes a metricset factory built with the specific overrides needed to instantiate the final metricset. Beats based on metricbeat can opt-in for this feature by adding WithLightModules() to its beat.Creator. This option is added to the licensed metricbeat, for that a new root command is instantiated.
- Loading branch information
Showing
19 changed files
with
981 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// 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 ( | ||
"github.com/pkg/errors" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
) | ||
|
||
// LightMetricSet contains the definition of a non-registered metric set | ||
type LightMetricSet struct { | ||
Name string | ||
Module string | ||
Default bool `config:"default"` | ||
Input struct { | ||
Module string `config:"module" validate:"required"` | ||
MetricSet string `config:"metricset" validate:"required"` | ||
Defaults interface{} `config:"defaults"` | ||
} `config:"input" validate:"required"` | ||
} | ||
|
||
// Registration obtains a metric set registration for this light metric set, this registration | ||
// contains a metric set factory that reprocess metric set creation taking into account the | ||
// light metric set defaults | ||
func (m *LightMetricSet) Registration(r *Register) (MetricSetRegistration, error) { | ||
registration, err := r.metricSetRegistration(m.Input.Module, m.Input.MetricSet) | ||
if err != nil { | ||
return registration, errors.Wrapf(err, | ||
"failed to start light metricset '%s/%s' using '%s/%s' metricset as input", | ||
m.Module, m.Name, | ||
m.Input.Module, m.Input.MetricSet) | ||
} | ||
|
||
originalFactory := registration.Factory | ||
registration.IsDefault = m.Default | ||
|
||
// Light modules factory has to override defaults and reproduce builder | ||
// functionality with the resulting configuration, it does: | ||
// - Override defaults | ||
// - Call module factory if registered (it wouldn't have been called | ||
// if light module is really a registered mixed module) | ||
// - Call host parser if defined (it would have already been called | ||
// without the light module defaults) | ||
// - Finally, call the original factory for the registered metricset | ||
registration.Factory = func(base BaseMetricSet) (MetricSet, error) { | ||
// Override default config on base module and metricset | ||
base.name = m.Name | ||
baseModule, err := m.baseModule(base.module) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "failed to create base module for light module '%s', using base module '%s'", m.Module, base.module.Name()) | ||
} | ||
base.module = baseModule | ||
|
||
// Run module factory if registered, it will be called once per | ||
// metricset, but it should be idempotent | ||
moduleFactory := r.moduleFactory(m.Input.Module) | ||
if moduleFactory != nil { | ||
module, err := moduleFactory(*baseModule) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "module factory for module '%s' failed while creating light metricset '%s/%s'", m.Input.Module, m.Module, m.Name) | ||
} | ||
base.module = module | ||
} | ||
|
||
// At this point host parser was already run, we need to run this again | ||
// with the overriden defaults | ||
if registration.HostParser != nil { | ||
base.hostData, err = registration.HostParser(base.module, base.host) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "host parser failed on light metricset factory for '%s/%s'", m.Module, m.Name) | ||
} | ||
base.host = base.hostData.Host | ||
} | ||
|
||
return originalFactory(base) | ||
} | ||
|
||
return registration, nil | ||
} | ||
|
||
// baseModule does the configuration overrides in the base module configuration | ||
// taking into account the light metric set default configurations | ||
func (m *LightMetricSet) baseModule(from Module) (*BaseModule, error) { | ||
baseModule := BaseModule{ | ||
name: m.Module, | ||
} | ||
var err error | ||
// Set defaults | ||
if baseModule.rawConfig, err = common.NewConfigFrom(m.Input.Defaults); err != nil { | ||
return nil, errors.Wrap(err, "invalid input defaults") | ||
} | ||
// Copy values from user configuration | ||
if err = from.UnpackConfig(baseModule.rawConfig); err != nil { | ||
return nil, errors.Wrap(err, "failed to copy values from user configuration") | ||
} | ||
// Update module configuration | ||
if err = baseModule.UnpackConfig(&baseModule.config); err != nil { | ||
return nil, errors.Wrap(err, "failed to set module configuration") | ||
} | ||
return &baseModule, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// 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 ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
) | ||
|
||
func TestLightMetricSetRegistration(t *testing.T) { | ||
cases := map[string]struct { | ||
module string | ||
metricSet string | ||
isDefault bool | ||
fail bool | ||
}{ | ||
"metricset is registered": { | ||
module: "foo", | ||
metricSet: "bar", | ||
fail: false, | ||
}, | ||
"metricset is registered and is default": { | ||
module: "foo", | ||
metricSet: "bar", | ||
isDefault: true, | ||
fail: false, | ||
}, | ||
"module is not registered": { | ||
module: "notexists", | ||
metricSet: "notexists", | ||
fail: true, | ||
}, | ||
"metricset is not registered": { | ||
module: "foo", | ||
metricSet: "notexists", | ||
fail: true, | ||
}, | ||
} | ||
|
||
fakeMetricSetFactory := func(b BaseMetricSet) (MetricSet, error) { return &b, nil } | ||
|
||
moduleName := "foo" | ||
metricSetName := "bar" | ||
lightMetricSetName := "metricset" | ||
lightModuleName := "module" | ||
|
||
r := NewRegister() | ||
r.MustAddMetricSet(moduleName, metricSetName, fakeMetricSetFactory) | ||
|
||
for title, c := range cases { | ||
t.Run(title, func(t *testing.T) { | ||
ms := LightMetricSet{ | ||
Name: lightMetricSetName, | ||
Module: lightModuleName, | ||
Default: c.isDefault, | ||
} | ||
ms.Input.Module = c.module | ||
ms.Input.MetricSet = c.metricSet | ||
ms.Input.Defaults = common.MapStr{ | ||
"query": common.MapStr{ | ||
"extra": "something", | ||
}, | ||
} | ||
|
||
registration, err := ms.Registration(r) | ||
if c.fail { | ||
assert.Error(t, err) | ||
return | ||
} | ||
require.NoError(t, err) | ||
|
||
// Check that registration has the light metricset settings | ||
assert.Equal(t, c.metricSet, registration.Name) | ||
assert.Equal(t, c.isDefault, registration.IsDefault) | ||
|
||
// Check that calling the factory with a registered base module: | ||
// - Does not modify original base module | ||
// - Does the proper overrides in the resulting metricset | ||
bm := baseModule(t, r, moduleName, metricSetName) | ||
moduleConfigBefore := bm.Module().Config().String() | ||
metricSet, err := registration.Factory(bm) | ||
|
||
assert.Equal(t, moduleConfigBefore, bm.Module().Config().String(), | ||
"original base module config should not change") | ||
require.NoError(t, err) | ||
require.NotNil(t, metricSet) | ||
|
||
assert.Equal(t, lightModuleName, metricSet.Module().Name()) | ||
assert.Equal(t, lightMetricSetName, metricSet.Name()) | ||
|
||
expectedQuery := QueryParams{ | ||
"default": "foo", | ||
"extra": "something", | ||
} | ||
query := metricSet.Module().Config().Query | ||
assert.Equal(t, expectedQuery, query) | ||
}) | ||
} | ||
} | ||
|
||
func baseModule(t *testing.T, r *Register, module, metricSet string) BaseMetricSet { | ||
origRegistration, err := r.metricSetRegistration(module, metricSet) | ||
require.NoError(t, err) | ||
|
||
c := DefaultModuleConfig() | ||
c.Module = module | ||
c.MetricSets = []string{metricSet} | ||
c.Query = QueryParams{"default": "foo"} | ||
raw, err := common.NewConfigFrom(c) | ||
require.NoError(t, err) | ||
baseModule, err := newBaseModuleFromConfig(raw) | ||
require.NoError(t, err) | ||
|
||
bm := BaseMetricSet{ | ||
name: "bar", | ||
module: &baseModule, | ||
registration: origRegistration, | ||
} | ||
return bm | ||
} |
Oops, something went wrong.