Skip to content

Commit fade3b6

Browse files
jpinsonneaujotak
andauthored
NETOBSERV-740: Metrics integration - console plugin frontend (#516)
* Add prom client vendor * NETOBSERV-739: Add prometheus queries This introduces prometheus as a new datasource, that can be used either as a replacement or as a complement of Loki. It doesn't require to change the frontend/backend API interface: on every frontend query, backend checks if that query is transposable to prometheus, and if so, runs it on prometheus, else falls back on loki (if it's enabled) There's some refactoring of the config and topology handlers to make place for prometheus. * Manage DnsFlagsResponseCode * Use explicit metrics defs; better error message * Use explicit metrics config; handle prom labels values * return datasources in query result * add datasource param & fix tests * remove disabled check * manage datasources * hide filters not available * Detect available filters without new config To this by probing with the existing filter encoder function * Do not show invalid groups Also, merge all "allowXXX" scope props into a single "allowedScopes" prop * fix front lint * Fix tab tooltip not showing * run prettier * Fix main remaining issues for prom datasource (backend) - Manage FlowDirection: first check if query can be performed using a metric agnostic to direction (Any); else, check both Ingress and Egress metric, and combine them in promQL with OR - Handle "getNamesForPrefix" with prom - Split "getTopologyFlows" in 2 parts for cyclo cplx - Improve error messages when query can't be performed using prometheus - Add tests * Do not show prom-unsupported as a Loki error * Fixes & address feedback - Consider known metrics labels when fetching label values from prom (else use Loki when possible, or raise error) - Avoid aggregate on app with prom - Fix issue when direction is unset in metrics defs - Fix datasources stats not merged on multiple queries - Add tests * Adding unit test on groups filtering * Add prom measurement metric * NETOBSERV-1652: retry with Loki on prom 401/403 * Fix percentile queries, metric name has no suffix In a previous implementation, metric names came with their "_bucket" prefix for histograms; now this isn't the case anymore but the query generation wasn't updated accordingly --------- Co-authored-by: Joel Takvorian <[email protected]>
1 parent 4579f42 commit fade3b6

File tree

103 files changed

+9679
-791
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+9679
-791
lines changed

cmd/plugin-backend.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"context"
45
"flag"
56
"fmt"
67
"os"
@@ -43,6 +44,10 @@ func main() {
4344
if err != nil {
4445
log.WithError(err).Fatal("error reading config file")
4546
}
47+
err = cfg.Validate()
48+
if err != nil {
49+
log.WithError(err).Fatal("invalid config")
50+
}
4651

4752
checker, err := cfg.GetAuthChecker()
4853
if err != nil {
@@ -55,5 +60,5 @@ func main() {
5560
KeyPath: cfg.Server.KeyPath,
5661
})
5762

58-
server.Start(cfg, checker)
63+
server.Start(context.Background(), cfg, checker)
5964
}

config/sample-config.yaml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
server:
22
port: 9001
33
metricsPort: 9002
4+
authCheck: none
45
loki:
56
url: http://localhost:3100
67
labels:
@@ -18,8 +19,25 @@ loki:
1819
# - SrcK8S_Zone
1920
# - DstK8S_Zone
2021
tenantID: netobserv
21-
authCheck: none
2222
useMocks: false
23+
prometheus:
24+
url: http://prometheus:9090
25+
timeout: 30s
26+
metrics:
27+
- enabled: false
28+
name: netobserv_node_egress_bytes_total
29+
type: counter
30+
valueField: Bytes
31+
labels:
32+
- SrcK8S_HostName
33+
- DstK8S_HostName
34+
- enabled: true
35+
name: netobserv_node_ingress_bytes_total
36+
type: counter
37+
valueField: Bytes
38+
labels:
39+
- SrcK8S_HostName
40+
- DstK8S_HostName
2341
frontend:
2442
recordTypes:
2543
- flowLog

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ require (
3232
github.com/google/gofuzz v1.2.0 // indirect
3333
github.com/google/uuid v1.3.0 // indirect
3434
github.com/josharian/intern v1.0.0 // indirect
35+
github.com/jpillora/backoff v1.0.0 // indirect
3536
github.com/mailru/easyjson v0.7.7 // indirect
3637
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3738
github.com/modern-go/reflect2 v1.0.2 // indirect
3839
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
40+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
3941
github.com/pmezard/go-difflib v1.0.0 // indirect
4042
github.com/prometheus/client_model v0.6.0 // indirect
4143
github.com/prometheus/procfs v0.12.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
3939
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
4040
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
4141
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
42+
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
43+
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
4244
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
4345
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
4446
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -59,6 +61,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
5961
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
6062
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
6163
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
64+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
65+
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
6266
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
6367
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
6468
github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg=

pkg/config/config.go

Lines changed: 112 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"errors"
45
"fmt"
56
"net/url"
67
"os"
@@ -9,6 +10,7 @@ import (
910

1011
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/auth"
1112
"github.com/netobserv/network-observability-console-plugin/pkg/kubernetes/client"
13+
"github.com/netobserv/network-observability-console-plugin/pkg/utils/constants"
1214
"github.com/sirupsen/logrus"
1315
"gopkg.in/yaml.v3"
1416
)
@@ -26,6 +28,34 @@ type Server struct {
2628
CORSMethods string `yaml:"corsMethods,omitempty" json:"corsMethods,omitempty"`
2729
CORSHeaders string `yaml:"corsHeaders,omitempty" json:"corsHeaders,omitempty"`
2830
CORSMaxAge string `yaml:"corsMaxAge,omitempty" json:"corsMaxAge,omitempty"`
31+
AuthCheck string `yaml:"authCheck,omitempty" json:"authCheck,omitempty"`
32+
}
33+
34+
type Prometheus struct {
35+
URL string `yaml:"url" json:"url"`
36+
Timeout Duration `yaml:"timeout,omitempty" json:"timeout,omitempty"`
37+
TokenPath string `yaml:"tokenPath,omitempty" json:"tokenPath,omitempty"`
38+
SkipTLS bool `yaml:"skipTls,omitempty" json:"skipTls,omitempty"`
39+
CAPath string `yaml:"caPath,omitempty" json:"caPath,omitempty"`
40+
ForwardUserToken bool `yaml:"forwardUserToken,omitempty" json:"forwardUserToken,omitempty"`
41+
Metrics []MetricInfo `yaml:"metrics,omitempty" json:"metrics,omitempty"`
42+
}
43+
44+
type FlowDirection string
45+
46+
const (
47+
Egress FlowDirection = "Egress"
48+
Ingress FlowDirection = "Ingress"
49+
AnyDirection FlowDirection = "Any"
50+
)
51+
52+
type MetricInfo struct {
53+
Enabled bool `yaml:"enabled" json:"enabled"`
54+
Name string `yaml:"name,omitempty" json:"name,omitempty"`
55+
Type string `yaml:"type,omitempty" json:"type,omitempty"`
56+
ValueField string `yaml:"valueField,omitempty" json:"valueField,omitempty"`
57+
Direction FlowDirection `yaml:"direction,omitempty" json:"direction,omitempty"`
58+
Labels []string `yaml:"labels,omitempty" json:"labels,omitempty"`
2959
}
3060

3161
type PortNaming struct {
@@ -50,10 +80,9 @@ type Column struct {
5080
}
5181

5282
type Filter struct {
53-
ID string `yaml:"id" json:"id"`
54-
Name string `yaml:"name" json:"name"`
55-
Component string `yaml:"component" json:"component"`
56-
83+
ID string `yaml:"id" json:"id"`
84+
Name string `yaml:"name" json:"name"`
85+
Component string `yaml:"component" json:"component"`
5786
Category string `yaml:"category,omitempty" json:"category,omitempty"`
5887
AutoCompleteAddsQuotes bool `yaml:"autoCompleteAddsQuotes,omitempty" json:"autoCompleteAddsQuotes,omitempty"`
5988
Hint string `yaml:"hint,omitempty" json:"hint,omitempty"`
@@ -96,13 +125,16 @@ type Frontend struct {
96125
Features []string `yaml:"features" json:"features"`
97126
Deduper Deduper `yaml:"deduper" json:"deduper"`
98127
Fields []FieldConfig `yaml:"fields" json:"fields"`
128+
DataSources []string `yaml:"dataSources" json:"dataSources"`
129+
PromLabels []string `yaml:"promLabels" json:"promLabels"`
99130
}
100131

101132
type Config struct {
102-
Loki Loki `yaml:"loki" json:"loki"`
103-
Frontend Frontend `yaml:"frontend" json:"frontend"`
104-
Server Server `yaml:"server,omitempty" json:"server,omitempty"`
105-
Path string `yaml:"-" json:"-"`
133+
Loki Loki `yaml:"loki" json:"loki"`
134+
Prometheus Prometheus `yaml:"prometheus" json:"prometheus"`
135+
Frontend Frontend `yaml:"frontend" json:"frontend"`
136+
Server Server `yaml:"server,omitempty" json:"server,omitempty"`
137+
Path string `yaml:"-" json:"-"`
106138
}
107139

108140
func ReadFile(version, date, filename string) (*Config, error) {
@@ -114,10 +146,13 @@ func ReadFile(version, date, filename string) (*Config, error) {
114146
MetricsPort: 9002,
115147
CORSOrigin: "*",
116148
CORSHeaders: "Origin, X-Requested-With, Content-Type, Accept",
149+
AuthCheck: "auto",
117150
},
118151
Loki: Loki{
119-
Timeout: Duration{Duration: 30 * time.Second},
120-
AuthCheck: "auto",
152+
Timeout: Duration{Duration: 30 * time.Second},
153+
},
154+
Prometheus: Prometheus{
155+
Timeout: Duration{Duration: 30 * time.Second},
121156
},
122157
Frontend: Frontend{
123158
BuildVersion: version,
@@ -141,6 +176,8 @@ func ReadFile(version, date, filename string) (*Config, error) {
141176
{Name: "SrcAddr", Type: "string"},
142177
{Name: "DstAddr", Type: "string"},
143178
},
179+
DataSources: []string{},
180+
PromLabels: []string{},
144181
},
145182
}
146183
if len(filename) == 0 {
@@ -155,56 +192,98 @@ func ReadFile(version, date, filename string) (*Config, error) {
155192
return nil, err
156193
}
157194

158-
cfg.Validate()
195+
if cfg.IsLokiEnabled() {
196+
cfg.Frontend.DataSources = append(cfg.Frontend.DataSources, string(constants.DataSourceLoki))
197+
}
198+
199+
if cfg.IsPromEnabled() {
200+
cfg.Frontend.DataSources = append(cfg.Frontend.DataSources, string(constants.DataSourceProm))
201+
labels := make(map[string]any)
202+
for _, m := range cfg.Prometheus.Metrics {
203+
if m.Enabled {
204+
for _, l := range m.Labels {
205+
labels[l] = true
206+
}
207+
}
208+
}
209+
for k := range labels {
210+
cfg.Frontend.PromLabels = append(cfg.Frontend.PromLabels, k)
211+
}
212+
}
213+
214+
return &cfg, err
215+
}
159216

160-
return &cfg, nil
217+
func (c *Config) IsLokiEnabled() bool {
218+
return c.Loki.URL != ""
161219
}
162220

163-
func (c *Config) Validate() {
164-
var configErrors []string
221+
func (c *Config) IsPromEnabled() bool {
222+
return c.Prometheus.URL != ""
223+
}
165224

166-
// check config required fields
167-
if len(c.Loki.Labels) == 0 {
168-
configErrors = append(configErrors, "labels cannot be empty")
225+
func (c *Config) Validate() error {
226+
if !c.IsLokiEnabled() && !c.IsPromEnabled() {
227+
return errors.New("neither Loki nor Prometheus is configured; at least one of them should have a URL defined")
169228
}
170229

171-
// parse config urls
172-
if len(c.Loki.URL) == 0 {
173-
configErrors = append(configErrors, "url cannot be empty")
174-
} else {
230+
var configErrors []string
231+
232+
if c.IsLokiEnabled() {
233+
log.Infof("Loki is enabled (%s)", c.Loki.URL)
234+
// check config required fields
235+
if len(c.Loki.Labels) == 0 {
236+
configErrors = append(configErrors, "labels cannot be empty")
237+
}
238+
239+
// parse config urls
175240
_, err := url.Parse(c.Loki.URL)
176241
if err != nil {
177242
configErrors = append(configErrors, "wrong Loki URL")
178243
}
244+
if len(c.Loki.StatusURL) > 0 {
245+
_, err := url.Parse(c.Loki.StatusURL)
246+
if err != nil {
247+
configErrors = append(configErrors, "wrong Loki status URL")
248+
}
249+
}
250+
} else {
251+
log.Info("Loki is disabled")
179252
}
180-
if len(c.Loki.StatusURL) > 0 {
181-
_, err := url.Parse(c.Loki.StatusURL)
253+
254+
if c.IsPromEnabled() {
255+
log.Infof("Prometheus is enabled (%s)", c.Prometheus.URL)
256+
// parse config urls
257+
_, err := url.Parse(c.Prometheus.URL)
182258
if err != nil {
183-
configErrors = append(configErrors, "wrong Loki status URL")
259+
configErrors = append(configErrors, "wrong Prometheus URL")
184260
}
261+
} else {
262+
log.Info("Prometheus is disabled")
185263
}
186264

187-
// crash on config errors
188265
if len(configErrors) > 0 {
189266
configErrors = append([]string{fmt.Sprintf("Config file has %d errors:\n", len(configErrors))}, configErrors...)
190-
log.Fatal(strings.Join(configErrors, "\n - "))
267+
return errors.New(strings.Join(configErrors, "\n - "))
191268
}
269+
270+
return nil
192271
}
193272

194273
func (c *Config) GetAuthChecker() (auth.Checker, error) {
195274
// parse config auth
196275
var checkType auth.CheckType
197-
if c.Loki.AuthCheck == "auto" {
198-
if c.Loki.ForwardUserToken {
199-
// FORWARD lokiAuth mode
200-
checkType = auth.CheckAuthenticated
201-
} else {
202-
// HOST or DISABLED lokiAuth mode
276+
if c.Server.AuthCheck == "auto" {
277+
// FORWARD mode
278+
checkType = auth.CheckAuthenticated
279+
if (c.IsLokiEnabled() && !c.Loki.ForwardUserToken) ||
280+
(c.IsPromEnabled() && !c.Prometheus.ForwardUserToken) {
281+
// HOST or DISABLED mode
203282
checkType = auth.CheckAdmin
204283
}
205284
log.Info(fmt.Sprintf("auth-check 'auto' resolved to '%s'", checkType))
206285
} else {
207-
checkType = auth.CheckType(c.Loki.AuthCheck)
286+
checkType = auth.CheckType(c.Server.AuthCheck)
208287
}
209288
if checkType == auth.CheckNone {
210289
log.Warn("INSECURE: auth checker is disabled")

pkg/config/loki.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ type Loki struct {
1717
StatusUserKeyPath string `yaml:"statusUserKeyPath,omitempty" json:"statusUserKeyPath,omitempty"`
1818
UseMocks bool `yaml:"useMocks,omitempty" json:"useMocks,omitempty"`
1919
ForwardUserToken bool `yaml:"forwardUserToken,omitempty" json:"forwardUserToken,omitempty"`
20-
AuthCheck string `yaml:"authCheck,omitempty" json:"authCheck,omitempty"`
2120
labelsMap map[string]struct{}
2221
}
2322

0 commit comments

Comments
 (0)