Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Harvest should support embedded exporters #2864

Merged
merged 1 commit into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/harvest/harvest.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ func getPollerPrometheusPort(pollerName string, opts *options) int {
return opts.promPort
}

if promPort, err = conf.GetPrometheusExporterPorts(pollerName, false); err != nil {
if promPort, err = conf.GetLastPromPort(pollerName, false); err != nil {
fmt.Println(err)
return 0
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/poller/poller.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,8 @@ func (p *Poller) newCollector(class string, object string, template *node.Node)
return col, err
}

// returns exporter that matches to name, if exporter is not loaded
// tries to load and return
// Returns the exporter with the matching name.
// If the exporter is not loaded, load and return it.
func (p *Poller) loadExporter(name string) exporter.Exporter {

var (
Expand Down
2 changes: 1 addition & 1 deletion cmd/tools/generate/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func generateDocker(kind int) {
filesd := make([]string, 0, len(conf.Config.PollersOrdered))

for _, v := range conf.Config.PollersOrdered {
port, _ := conf.GetPrometheusExporterPorts(v, true)
port, _ := conf.GetLastPromPort(v, true)
pollerInfo := PollerInfo{
ServiceName: normalizeContainerNames(v),
PollerName: v,
Expand Down
28 changes: 15 additions & 13 deletions harvest.cue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package harvest

Exporters: [Name=_]: #Prom | #Influx

#ExporterDefs: string | #Prom | #Influx

label: [string]: string

#Auth: {
Expand Down Expand Up @@ -77,17 +79,17 @@ Pollers: [Name=_]: #Poller
credentials_file?: string
credentials_script?: #CredentialsScript
datacenter?: string
exporters: [...string]
is_kfs?: bool
labels?: [...label]
log: [...string]
log_max_bytes?: int
log_max_files?: int
password?: string
prefer_zapi?: bool
ssl_cert?: string
ssl_key?: string
tls_min_version?: string
use_insecure_tls?: bool
username?: string
exporters: [...#ExporterDefs]
is_kfs?: bool
labels?: [...label]
log: [...string]
log_max_bytes?: int
log_max_files?: int
password?: string
prefer_zapi?: bool
ssl_cert?: string
ssl_key?: string
tls_min_version?: string
use_insecure_tls?: bool
username?: string
}
2 changes: 1 addition & 1 deletion integration/test/metric_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestPollerMetrics(t *testing.T) {
}
var duplicateMetrics []string
for _, pollerName := range conf.Config.PollersOrdered {
port, _ := conf.GetPrometheusExporterPorts(pollerName, true)
port, _ := conf.GetLastPromPort(pollerName, true)
portString := strconv.Itoa(port)
var validCounters = 0
uniqueSetOfMetricLabels := make(map[string]bool)
Expand Down
88 changes: 63 additions & 25 deletions pkg/conf/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func LoadHarvestConfig(configPath string) (string, error) {
return "", errors.Join(duplicates...)
}

// After processing all the configuration files, check if Config.Pollers is still empty.
// After processing all the configuration files, check if the Config.Pollers parameter is still empty.
if len(Config.Pollers) == 0 {
return "", errs.New(errs.ErrConfig, "[Pollers] section not found")
}
Expand All @@ -137,9 +137,27 @@ func LoadHarvestConfig(configPath string) (string, error) {
for i, name := range Config.PollersOrdered {
Config.Pollers[name].promIndex = i
}

fixupExporters()
return configPath, nil
}

func fixupExporters() {
for _, pollerName := range Config.PollersOrdered {
poller := Config.Pollers[pollerName]
for i, e := range poller.ExporterDefs {
exporterName := e.name
if exporterName == "" {
// This is an embedded exporter, synthesize a name for it
exporterName = fmt.Sprintf("%s-%d", pollerName, i)
Config.Exporters[exporterName] = e.Exporter
}

poller.Exporters = append(poller.Exporters, exporterName)
}
}
}

func unmarshalConfig(contents []byte) (*HarvestConfig, error) {
var (
cfg HarvestConfig
Expand Down Expand Up @@ -285,8 +303,9 @@ func GetHarvestLogPath() string {
return cmp.Or(os.Getenv("HARVEST_LOGS"), "/var/log/harvest/")
}

// GetPrometheusExporterPorts returns the Prometheus port for the given poller
func GetPrometheusExporterPorts(pollerName string, validatePortInUse bool) (int, error) {
// GetLastPromPort returns the Prometheus port for the given poller
// If multiple Prometheus exporters are configured for a poller, the port for the last exporter is returned.
func GetLastPromPort(pollerName string, validatePortInUse bool) (int, error) {
var port int
var isPrometheusExporterConfigured bool

Expand All @@ -299,29 +318,28 @@ func GetPrometheusExporterPorts(pollerName string, validatePortInUse bool) (int,
}

exporters := poller.Exporters
if len(exporters) > 0 {
for _, e := range exporters {
exporter := Config.Exporters[e]
if exporter.Type == "Prometheus" {
isPrometheusExporterConfigured = true
if exporter.PortRange != nil {
ports := promPortRangeMapping[e]
preferredPort := exporter.PortRange.Min + poller.promIndex
_, isFree := ports.freePorts[preferredPort]
if isFree {
port = preferredPort
delete(ports.freePorts, preferredPort)
break
}
for k := range ports.freePorts {
port = k
delete(ports.freePorts, k)
break
}
} else if exporter.Port != nil && *exporter.Port != 0 {
port = *exporter.Port
for i := len(exporters) - 1; i >= 0; i-- {
e := exporters[i]
exporter := Config.Exporters[e]
if exporter.Type == "Prometheus" {
isPrometheusExporterConfigured = true
if exporter.PortRange != nil {
ports := promPortRangeMapping[e]
preferredPort := exporter.PortRange.Min + poller.promIndex
_, isFree := ports.freePorts[preferredPort]
if isFree {
port = preferredPort
delete(ports.freePorts, preferredPort)
break
}
for k := range ports.freePorts {
port = k
delete(ports.freePorts, k)
break
}
} else if exporter.Port != nil && *exporter.Port != 0 {
port = *exporter.Port
break
}
}
}
Expand Down Expand Up @@ -458,6 +476,25 @@ type CertificateScript struct {
Timeout string `yaml:"timeout,omitempty"`
}

type ExportDef struct {
name string
Exporter
}

func (e *ExportDef) UnmarshalYAML(n *yaml.Node) error {
if n.Kind == yaml.MappingNode {
var aExporter *Exporter
err := n.Decode(&aExporter)
if err != nil {
return fmt.Errorf("error unmarshalling embedded exporter: %w", err)
}
e.Exporter = *aExporter
} else if n.Kind == yaml.ScalarNode && n.ShortTag() == "!!str" {
e.name = n.Value
}
return nil
}

type Poller struct {
Addr string `yaml:"addr,omitempty"`
APIVersion string `yaml:"api_version,omitempty"`
Expand All @@ -470,7 +507,7 @@ type Poller struct {
CredentialsScript CredentialsScript `yaml:"credentials_script,omitempty"`
CertificateScript CertificateScript `yaml:"certificate_script,omitempty"`
Datacenter string `yaml:"datacenter,omitempty"`
Exporters []string `yaml:"exporters,omitempty"`
ExporterDefs []ExportDef `yaml:"exporters,omitempty"`
IsKfs bool `yaml:"is_kfs,omitempty"`
Labels *[]map[string]string `yaml:"labels,omitempty"`
LogMaxBytes int64 `yaml:"log_max_bytes,omitempty"`
Expand All @@ -485,6 +522,7 @@ type Poller struct {
Username string `yaml:"username,omitempty"`
PreferZAPI bool `yaml:"prefer_zapi,omitempty"`
ConfPath string `yaml:"conf_path,omitempty"`
Exporters []string `yaml:"-"`
promIndex int
Name string
}
Expand Down
55 changes: 49 additions & 6 deletions pkg/conf/conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/netapp/harvest/v2/pkg/tree/node"
"os"
"reflect"
"slices"
"sort"
"strconv"
"strings"
Expand All @@ -12,7 +13,7 @@ import (

var testYml = "../../cmd/tools/doctor/testdata/testConfig.yml"

func TestGetPrometheusExporterPorts(t *testing.T) {
func TestGetLastPromPort(t *testing.T) {
TestLoadHarvestConfig(testYml)
type args struct {
pollerNames []string
Expand All @@ -29,23 +30,23 @@ func TestGetPrometheusExporterPorts(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for i, v := range tt.args.pollerNames {
got, err := GetPrometheusExporterPorts(v, true)
got, err := GetLastPromPort(v, true)
if (err != nil) != tt.wantErr[i] {
t.Errorf("GetPrometheusExporterPorts() error = %v, wantErr %v", err, tt.wantErr)
t.Errorf("GetLastPromPort() error = %v, wantErr %v", err, tt.wantErr)
return
}
if err == nil && got == 0 {
t.Errorf("GetPrometheusExporterPorts() got = %v, want %s", got, "non zero value")
t.Errorf("GetLastPromPort() got = %v, want %s", got, "non zero value")
}
}
})
}
}

func TestGetPrometheusExporterPortsIssue284(t *testing.T) {
func TestGetLastPromPortIssue284(t *testing.T) {
TestLoadHarvestConfig("../../cmd/tools/doctor/testdata/issue-284.yml")
loadPrometheusExporterPortRangeMapping(false)
got, _ := GetPrometheusExporterPorts("issue-284", false)
got, _ := GetLastPromPort("issue-284", false)
if got != 0 {
t.Fatalf("expected port to be 0 but was %d", got)
}
Expand Down Expand Up @@ -464,3 +465,45 @@ func TestEmptyPoller(t *testing.T) {
}
}
}

func TestEmbeddedExporter(t *testing.T) {
t.Helper()
resetConfig()

configYaml := "testdata/issue_2852_direct_exporters.yml"
_, err := LoadHarvestConfig(configYaml)
if err != nil {
t.Fatalf("got error loading config: %s, want no errors", err)
}

p, err := PollerNamed("u2")
if err != nil {
t.Fatalf("got no poller, want poller named=u2")
}
if len(p.Exporters) != 3 {
t.Errorf("got %d exporters, want 3", len(p.Exporters))
}

port, err := GetLastPromPort("u2", false)
if err != nil {
t.Fatalf("got error: %v, want no error", err)
}
if port != 12990 {
t.Errorf("got port=%d, want port=12990", port)
}

uniqueExporters := GetUniqueExporters(p.Exporters)
want := []string{"u2-1", "u2-2})"}
if slices.Equal(uniqueExporters, want) {
t.Errorf("got %v, want %v", uniqueExporters, want)
}

// Test that the last default Prometheus exporter is used when no exporters are defined for a poller
port, err = GetLastPromPort("u3", false)
if err != nil {
t.Fatalf("got error: %v, want no error", err)
}
if port != 32990 {
t.Errorf("got port=%d, want port=32990", port)
}
}
26 changes: 26 additions & 0 deletions pkg/conf/testdata/issue_2852_direct_exporters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
Exporters:
prometheus1:
exporter: Prometheus
addr: 0.0.0.0
port_range: 2000-2030

Defaults:
exporters:
- prometheus1
- exporter: Prometheus
port: 32990

Pollers:
u2:
addr: 10.193.48.11
exporters:
- prometheus1
- exporter: Prometheus
port: 12990
- exporter: InfluxDB
addr: localhost
bucket: harvest
org: harvest

u3:
addr: 10.0.1.1