From aa9d94f97f81dd8f3a78feadbb9cdf17103005a6 Mon Sep 17 00:00:00 2001 From: genofire Date: Thu, 14 Apr 2022 08:04:41 +0200 Subject: [PATCH] [TASK] add output prometheus-sd (service discovery) (#213) --- cmd/config_test.go | 2 +- config_example.toml | 13 ++++ docs/docs_configuration.md | 61 +++++++++++++++++++ output/all/main.go | 1 + output/prometheus-sd/output.go | 93 +++++++++++++++++++++++++++++ output/prometheus-sd/output_test.go | 90 ++++++++++++++++++++++++++++ 6 files changed, 259 insertions(+), 1 deletion(-) create mode 100644 output/prometheus-sd/output.go create mode 100644 output/prometheus-sd/output_test.go diff --git a/cmd/config_test.go b/cmd/config_test.go index 550dafda..349b1fb4 100644 --- a/cmd/config_test.go +++ b/cmd/config_test.go @@ -27,7 +27,7 @@ func TestReadConfig(t *testing.T) { assert.Contains(config.Respondd.Sites["ffhb"].Domains, "city") // Test output plugins - assert.Len(config.Nodes.Output, 5) + assert.Len(config.Nodes.Output, 6) outputs := config.Nodes.Output["meshviewer"].([]map[string]interface{}) assert.Len(outputs, 1) meshviewer := outputs[0] diff --git a/config_example.toml b/config_example.toml index 6be777ce..8c1604e3 100644 --- a/config_example.toml +++ b/config_example.toml @@ -158,6 +158,19 @@ path = "/var/www/html/meshviewer/data/nodelist.json" # WARNING: if it is not set, it will publish contact information of other persons no_owner = true +# definition for prometheus-sd.json +[[nodes.output.prometheus-sd]] +enable = true +path = "/var/www/html/meshviewer/data/prometheus-sd.json" +# ip = lates recieved ip, node_id = node id from host +target_address = "ip" + +# Labels of the data (optional) +[nodes.output.prometheus-sd.labels] +#labelname1 = "labelvalue 1" +## some useful e.g.: +#hosts = "ffhb" +#service = "yanic" # definition for raw.json [[nodes.output.raw]] diff --git a/docs/docs_configuration.md b/docs/docs_configuration.md index 754778aa..7e73ca0e 100644 --- a/docs/docs_configuration.md +++ b/docs/docs_configuration.md @@ -545,6 +545,67 @@ path = "/var/www/html/meshviewer/data/nodelist.json" ``` {% endmethod %} + + +## [[nodes.output.prometheus-sd]] +{% method %} +The Prometheus Service Discovery (SD) output is a output with the list of addresses of the nodes to use them in later exporter by prometheus. +For usage in Prometheus read there Documentation [Use file-based service discovery to discover scrape targets](https://prometheus.io/docs/guides/file-sd/). +{% sample lang="toml" %} +```toml +[[nodes.output.prometheus-sd]] +enable = false +path = "/var/www/html/meshviewer/data/prometheus-sd.json" +target_address = "ip" + +[nodes.output.prometheus-sd.labels] +labelname1 = "labelvalue 1" +# some useful e.g.: +hosts = "ffhb" +service = "yanic" +``` +{% endmethod %} + + +### path +{% method %} +The path, where to store prometheus-sd.json +{% sample lang="toml" %} +```toml +path = "/var/www/html/meshviewer/data/prometheus-sd.json" +``` +{% endmethod %} + +### target_address +{% method %} +In the prometheus-sd.json the usage of which information of the node as targets (address). + +Use the `node_id` as value, to put the Node ID into the target list as address. +Use the `ip` as value to put the last IP address into the target list from where the respondd message is recieved (maybe a link-local address). +Default value is `ip`. + +{% sample lang="toml" %} +```toml +path = "/var/www/html/meshviewer/data/prometheus-sd.json" +``` +{% endmethod %} + + +### [nodes.output.prometheus-sd.labels] +{% method %} +You could optional set manuelle labels with inserting into a prometheus-sd.json. +Useful if you want to identify the yanic instance when you use multiple own on the same prometheus database (e.g. multisites). + +{% sample lang="toml" %} +```toml +labelname1 = "labelvalue 1" +# some useful e.g.: +hosts = "ffhb" +service = "yanic" +``` +{% endmethod %} + + ## [[nodes.output.raw]] {% method %} This output takes the respondd response as sent by the node and includes it in a JSON document. diff --git a/output/all/main.go b/output/all/main.go index 309576dd..03084f34 100644 --- a/output/all/main.go +++ b/output/all/main.go @@ -5,6 +5,7 @@ import ( _ "yanic/output/meshviewer" _ "yanic/output/meshviewer-ffrgb" _ "yanic/output/nodelist" + _ "yanic/output/prometheus-sd" _ "yanic/output/raw" _ "yanic/output/raw-jsonl" ) diff --git a/output/prometheus-sd/output.go b/output/prometheus-sd/output.go new file mode 100644 index 00000000..7e595c0b --- /dev/null +++ b/output/prometheus-sd/output.go @@ -0,0 +1,93 @@ +package prometheus_sd + +import ( + "errors" + + "yanic/output" + "yanic/runtime" +) + +type Output struct { + output.Output + path string + targetType TargetAddressType + labels map[string]interface{} +} + +type Config map[string]interface{} + +func (c Config) Path() string { + if path, ok := c["path"]; ok { + return path.(string) + } + return "" +} + +type TargetAddressType string + +const ( + TargetAddressIP TargetAddressType = "ip" + TargetAddressNodeID TargetAddressType = "node_id" +) + +func (c Config) TargetAddress() TargetAddressType { + if v, ok := c["target_address"]; ok { + return TargetAddressType(v.(string)) + } + return TargetAddressIP +} + +func (c Config) Labels() map[string]interface{} { + if v, ok := c["labels"]; ok { + return v.(map[string]interface{}) + } + return nil +} + +func init() { + output.RegisterAdapter("prometheus-sd", Register) +} + +func Register(configuration map[string]interface{}) (output.Output, error) { + config := Config(configuration) + + if path := config.Path(); path != "" { + return &Output{ + path: path, + targetType: config.TargetAddress(), + labels: config.Labels(), + }, nil + } + return nil, errors.New("no path given") + +} + +type Targets struct { + Targets []string `json:"targets"` + Labels map[string]interface{} `json:"labels,omitempty"` +} + +func (o *Output) Save(nodes *runtime.Nodes) { + nodes.RLock() + defer nodes.RUnlock() + + targets := &Targets{ + Targets: []string{}, + Labels: o.labels, + } + if o.targetType == TargetAddressNodeID { + for _, n := range nodes.List { + if ni := n.Nodeinfo; ni != nil { + targets.Targets = append(targets.Targets, ni.NodeID) + } + } + } else { + for _, n := range nodes.List { + if addr := n.Address; addr != nil { + targets.Targets = append(targets.Targets, addr.IP.String()) + } + } + } + + runtime.SaveJSON([]interface{}{targets}, o.path) +} diff --git a/output/prometheus-sd/output_test.go b/output/prometheus-sd/output_test.go new file mode 100644 index 00000000..b1941d87 --- /dev/null +++ b/output/prometheus-sd/output_test.go @@ -0,0 +1,90 @@ +package prometheus_sd + +import ( + "os" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "yanic/data" + "yanic/runtime" +) + +func TestOutput(t *testing.T) { + assert := assert.New(t) + + out, err := Register(map[string]interface{}{}) + assert.Error(err) + assert.Nil(out) + + nodes := runtime.NewNodes(&runtime.NodesConfig{}) + ipAddress, err := net.ResolveUDPAddr("udp6", "[fe80::20de:a:3ac%eth0]:1001") + assert.NoError(err) + nodes.AddNode(&runtime.Node{ + Online: true, + Address: ipAddress, + Nodeinfo: &data.Nodeinfo{ + NodeID: "node_a", + Network: data.Network{ + Mac: "node:a:mac", + Mesh: map[string]*data.NetworkInterface{ + "bat0": { + Interfaces: struct { + Wireless []string `json:"wireless,omitempty"` + Other []string `json:"other,omitempty"` + Tunnel []string `json:"tunnel,omitempty"` + }{ + Wireless: []string{"node:a:mac:wifi"}, + Tunnel: []string{"node:a:mac:vpn"}, + Other: []string{"node:a:mac:lan"}, + }, + }, + }, + }, + }, + Neighbours: &data.Neighbours{ + NodeID: "node_a", + Batadv: map[string]data.BatadvNeighbours{ + "node:a:mac:wifi": { + Neighbours: map[string]data.BatmanLink{ + "node:b:mac:wifi": {Tq: 153}, + }, + }, + "node:a:mac:lan": { + Neighbours: map[string]data.BatmanLink{ + "node:b:mac:lan": {Tq: 51}, + }, + }, + }, + }, + }) + + // IP + out, err = Register(map[string]interface{}{ + "path": "/tmp/prometheus_sd.json", + }) + os.Remove("/tmp/prometheus_sd.json") + assert.NoError(err) + assert.NotNil(out) + + out.Save(nodes) + _, err = os.Stat("/tmp/prometheus_sd.json") + assert.NoError(err) + + // NodeID + out, err = Register(map[string]interface{}{ + "target_address": "node_id", + "path": "/tmp/prometheus_sd.json", + "labels": map[string]interface{}{ + "hosts": "ffhb", + "service": "yanic", + }, + }) + os.Remove("/tmp/prometheus_sd.json") + assert.NoError(err) + assert.NotNil(out) + + out.Save(nodes) + _, err = os.Stat("/tmp/prometheus_sd.json") + assert.NoError(err) +}